Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>/*
* Copyright 2010 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.javascript.jscomp.CodingConvention.Bind;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.regex.Pattern;
/**
* A peephole optimization that minimizes code by simplifying conditional
* expressions, replacing IFs with HOOKs, replacing object constructors
* with literals, and simplifying returns.
*
*/
class PeepholeSubstituteAlternateSyntax
extends AbstractPeepholeOptimization {
private static final CodeGenerator REGEXP_ESCAPER =
CodeGenerator.forCostEstimation(
null /* blow up if we try to produce code */);
private final boolean late;
private static final int STRING_SPLIT_OVERHEAD = ".split('.')".length();
static final DiagnosticType INVALID_REGULAR_EXPRESSION_FLAGS =
DiagnosticType.warning(
"JSC_INVALID_REGULAR_EXPRESSION_FLAGS",
"Invalid flags to RegExp constructor: {0}");
/**
* @param late When late is false, this mean we are currently running before
* most of the other optimizations. In this case we would avoid optimizations
* that would make the code harder to analyze (such as using string splitting,
* merging statements with commas, etc). When this is true, we would
* do anything to minimize for size.
*/
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>
PeepholeSubstituteAlternateSyntax(boolean late) {
this.late = late;
}
/**
* Tries apply our various peephole minimizations on the passed in node.
*/
@Override
@SuppressWarnings("fallthrough")
public Node optimizeSubtree(Node node) {
switch(node.getType()) {
case Token.TRUE:
case Token.FALSE:
return reduceTrueFalse(node);
case Token.NEW:
node = tryFoldStandardConstructors(node);
if (!node.isCall()) {
return node;
}
// Fall through on purpose because tryFoldStandardConstructors() may
Node value = callTarget.getNext();
if (value != null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
Bind bind = getCodingConvention().describeFunctionBind(callTarget, false);
if (bind != null) {
// replace the call target
bind.target.detachFromParent();
n.replaceChild(callTarget, bind.target);
callTarget = bind.target;
// push the parameters
addParameterAfter(bind.parameters, callTarget);
// add the this value before the parameters if necessary
if (bind.thisValue != null && !NodeUtil.isUndefined(bind.thisValue)) {
// rewrite from "fn(a, b)" to "fn.call(thisValue, a, b)"
Node newCallTarget = IR.getprop(
callTarget.cloneTree(),
IR.string("call").srcref(callTarget));
n.replaceChild(callTarget, newCallTarget);
n.addChildAfter(bind.thisValue.cloneTree(), newCallTarget);
n.putBooleanProp(Node.FREE_CALL
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>, false);
} else {
n.putBooleanProp(Node.FREE_CALL, true);
}
reportCodeChange();
}
return n;
}
private void addParameterAfter(Node parameterList, Node after) {
if (parameterList != null) {
// push the last parameter to the head of the list first.
addParameterAfter(parameterList.getNext(), after);
after.getParent().addChildAfter(parameterList.cloneTree(), after);
}
}
private Node trySplitComma(Node n) {
if (late) {
return n;
}
Node parent = n.getParent();
Node left = n.getFirstChild();
Node right = n.getLastChild();
if (parent.isExprResult()
&& !parent.getParent().isLabel()) {
// split comma
n.detachChildren();
// Replace the original expression with the left operand.
parent.replaceChild(n, left);
// Add the right expression afterward.
Node newStatement = IR.exprResult(right);
newStatement.copyInformationFrom(n);
//This modifies outside the subtree, which is not
//desirable in a peephole optimization.
parent.getParent().addChildAfter(newStatement, parent);
reportCodeChange();
return left;
} else {
return n;
}
}
/**
* Use "void 0" in place of "undefined"
*/
private Node tryReplaceUndefined(Node n) {
// TODO(johnlenz): consider doing this as a normalization.
if (isASTNormalized()
&& NodeUtil.isUndefined(n)
&& !NodeUtil.isLValue(n)) {
Node replacement = NodeUtil.newUndefinedNode(n);
n.getParent().replaceChild(n, replacement);
reportCodeChange();
return replacement;
}
return n;
}
/**
* Reduce "return undefined" or "return void 0" to simply "return".
*
* @return The original node, maybe simplified.
*/
private Node tryReduceReturn(Node n) {
Node result = n.getFirstChild();
if (result != null) {
switch (result.getType()) {
case Token.VOID:
Node operand = result.getFirstChild();
if (!mayHaveSideEffects(operand))
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> {
n.removeFirstChild();
reportCodeChange();
}
break;
case Token.NAME:
String name = result.getString();
if (name.equals("undefined")) {
n.removeFirstChild();
reportCodeChange();
}
break;
}
}
return n;
}
private static final ImmutableSet<String> STANDARD_OBJECT_CONSTRUCTORS =
// String, Number, and Boolean functions return non-object types, whereas
// new String, new Number, and new Boolean return object types, so don't
// include them here.
ImmutableSet.of(
"Object",
"Array",
"RegExp",
"Error"
);
/**
* Fold "new Object()" to "Object()".
*/
private Node tryFoldStandardConstructors(Node n) {
Preconditions.checkState(n.isNew());
// If name normalization has been run then we know that
// new Object() does in fact refer to what we think it is
// and not some custom-defined Object().
if (isASTNormalized()) {
if (n.getFirstChild().isName()) {
String className = n.getFirstChild().getString();
if (STANDARD_OBJECT_CONSTRUCTORS.contains(className)) {
n.setType(Token.CALL);
n.putBooleanProp(Node.FREE_CALL, true);
reportCodeChange();
}
}
}
return n;
}
/**
* Replaces a new Array or Object node with an object literal, unless the
* call to Array or Object is to a local function with the same name.
*/
private Node tryFoldLiteralConstructor(Node n) {
Preconditions.checkArgument(n.isCall()
|| n.isNew());
Node constructorNameNode = n.getFirstChild();
Node newLiteralNode = null;
// We require the AST to be normalized to ensure that, say,
// Object() really refers to the built-in Object constructor
// and not a user-defined constructor with the same name.
if (isASTNormalized() && Token.NAME == constructorNameNode.getType()) {
String className = constructorNameNode.getString();
if ("RegExp".equals(className)) {
// "RegExp("boo", "g")" --> /boo/g
return tryFoldRegularExpressionConstructor(n
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>);
} else {
boolean constructorHasArgs = constructorNameNode.getNext() != null;
if ("Object".equals(className) && !constructorHasArgs) {
// "Object()" --> "{}"
newLiteralNode = IR.objectlit();
} else if ("Array".equals(className)) {
// "Array(arg0, arg1, ...)" --> "[arg0, arg1, ...]"
Node arg0 = constructorNameNode.getNext();
FoldArrayAction action = isSafeToFoldArrayConstructor(arg0);
if (action == FoldArrayAction.SAFE_TO_FOLD_WITH_ARGS ||
action == FoldArrayAction.SAFE_TO_FOLD_WITHOUT_ARGS) {
newLiteralNode = IR.arraylit();
n.removeChildren();
if (action == FoldArrayAction.SAFE_TO_FOLD_WITH_ARGS) {
newLiteralNode.addChildrenToFront(arg0);
}
}
}
if (newLiteralNode != null) {
n.getParent().replaceChild(n, newLiteralNode);
reportCodeChange();
return newLiteralNode;
}
}
}
return n;
}
private static enum FoldArrayAction {
NOT_SAFE_TO_FOLD, SAFE_TO_FOLD_WITH_ARGS, SAFE_TO_FOLD_WITHOUT_ARGS}
/**
* Checks if it is safe to fold Array() constructor into []. It can be
* obviously done, if the initial constructor has either no arguments or
* at least two. The remaining case may be unsafe since Array(number)
* actually reserves memory for an empty array which contains number elements.
*/
private static FoldArrayAction isSafeToFoldArrayConstructor(Node arg) {
FoldArrayAction action = FoldArrayAction.NOT_SAFE_TO_FOLD;
if (arg == null) {
action = FoldArrayAction.SAFE_TO_FOLD_WITHOUT_ARGS;
} else if (arg.getNext() != null) {
action = FoldArrayAction.SAFE_TO_FOLD_WITH_ARGS;
} else {
switch (arg.getType()) {
case Token.STRING:
// "Array('a')" --> "['a']"
action = FoldArrayAction.SAFE_TO_FOLD_WITH_ARGS;
break;
case Token.NUMBER:
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> // "Array(0)" --> "[]"
if (arg.getDouble() == 0) {
action = FoldArrayAction.SAFE_TO_FOLD_WITHOUT_ARGS;
}
break;
case Token.ARRAYLIT:
// "Array([args])" --> "[[args]]"
action = FoldArrayAction.SAFE_TO_FOLD_WITH_ARGS;
break;
default:
}
}
return action;
}
private Node tryFoldRegularExpressionConstructor(Node n) {
Node parent = n.getParent();
Node constructor = n.getFirstChild();
Node pattern = constructor.getNext(); // e.g. ^foobar$
Node flags = null != pattern ? pattern.getNext() : null; // e.g. gi
if (null == pattern || (null != flags && null != flags.getNext())) {
// too few or too many arguments
return n;
}
if (// is pattern folded
pattern.isString()
// make sure empty pattern doesn't fold to //
&& !"".equals(pattern.getString())
// NOTE(nicksantos): Make sure that the regexp isn't longer than
// 100 chars, or it blows up the regexp parser in Opera 9.2.
&& pattern.getString().length() < 100
&& (null == flags || flags.isString())
// don't escape patterns with Unicode escapes since Safari behaves badly
// (read can't parse or crashes) on regex literals with Unicode escapes
&& (isEcmaScript5OrGreater()
|| !containsUnicodeEscape(pattern.getString()))) {
// Make sure that / is escaped, so that it will fit safely in /brackets/
// and make sure that no LineTerminatorCharacters appear literally inside
// the pattern.
// pattern is a string value with \\ and similar already escaped
pattern = makeForwardSlashBracketSafe(pattern);
Node regexLiteral;
if (null == flags || "".equals(flags.getString())) {
// fold to /foobar/
regexLiteral = IR.regexp(pattern);
} else {
// fold to /foobar/gi
if (!areValidRegexpFlags(flags.getString())) {
report(INVALID_REGULAR_EXPRESSION_FLAGS, flags);
return n;
}
if (!areSafeFlagsToFold
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>(flags.getString())) {
return n;
}
n.removeChild(flags);
regexLiteral = IR.regexp(pattern, flags);
}
parent.replaceChild(n, regexLiteral);
reportCodeChange();
return regexLiteral;
}
return n;
}
private Node reduceTrueFalse(Node n) {
if (late) {
Node not = IR.not(IR.number(n.isTrue() ? 0 : 1));
not.copyInformationFromForTree(n);
n.getParent().replaceChild(n, not);
reportCodeChange();
return not;
}
return n;
}
private Node tryMinimizeArrayLiteral(Node n) {
boolean allStrings = true;
for (Node cur = n.getFirstChild(); cur != null; cur = cur.getNext()) {
if (!cur.isString()) {
allStrings = false;
}
}
if (allStrings) {
return tryMinimizeStringArrayLiteral(n);
} else {
return n;
}
}
private Node tryMinimizeStringArrayLiteral(Node n) {
if (!late) {
return n;
}
int numElements = n.getChildCount();
// We save two bytes per element.
int saving = numElements * 2 - STRING_SPLIT_OVERHEAD;
if (saving <= 0) {
return n;
}
String[] strings = new String[n.getChildCount()];
int idx = 0;
for (Node cur = n.getFirstChild(); cur != null; cur = cur.getNext()) {
strings[idx++] = cur.getString();
}
// These delimiters are chars that appears a lot in the program therefore
// probably have a small Huffman encoding.
String delimiter = pickDelimiter(strings);
if (delimiter != null) {
String template = Joiner.on(delimiter).join(strings);
Node call = IR.call(
IR.getprop(
IR.string(template),
IR.string("split")),
IR.string("" + delimiter));
call.copyInformationFromForTree(n);
n.getParent().replaceChild(n, call);
reportCodeChange();
return call;
}
return n;
}
/**
* Find a delimiter that does not occur in
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>BLOCK);
cc.endFunction(context == Context.STATEMENT);
if (funcNeedsParens) {
add(")");
}
break;
case Token.GETTER_DEF:
case Token.SETTER_DEF:
Preconditions.checkState(n.getParent().isObjectLit());
Preconditions.checkState(childCount == 1);
Preconditions.checkState(first.isFunction());
// Get methods are unnamed
Preconditions.checkState(first.getFirstChild().getString().isEmpty());
if (type == Token.GETTER_DEF) {
// Get methods have no parameters.
Preconditions.checkState(!first.getChildAtIndex(1).hasChildren());
add("get ");
} else {
// Set methods have one parameter.
Preconditions.checkState(first.getChildAtIndex(1).hasOneChild());
add("set ");
}
// The name is on the GET or SET node.
String name = n.getString();
Node fn = first;
Node parameters = fn.getChildAtIndex(1);
Node body = fn.getLastChild();
// Add the property name.
if (!n.isQuotedString() &&
TokenStream.isJSIdentifier(name) &&
// do not encode literally any non-literal characters that were
// Unicode escaped.
NodeUtil.isLatin(name)) {
add(name);
} else {
// Determine if the string is a simple number.
double d = getSimpleNumber(name);
if (!Double.isNaN(d)) {
cc.addNumber(d);
} else {
addJsString(n);
}
}
add(parameters);
add(body, Context.PRESERVE_BLOCK);
break;
case Token.SCRIPT:
case Token.BLOCK: {
if (n.getClass() != Node.class) {
throw new Error("Unexpected Node subclass.");
}
boolean preserveBlock = context == Context.PRESERVE_BLOCK;
if (preserveBlock) {
cc.beginBlock();
}
boolean preferLineBreaks =
type == Token.SCRIPT ||
(type == Token.BLOCK &&
!preserveBlock &&
n.getParent() != null &&
n.getParent().isScript());
for (Node c = first; c != null; c = c.getNext()) {
add(c, Context.
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>
throw new Error("Unknown type " + type + "\n" + n.toStringTree());
}
cc.endSourceMapping(n);
}
/**
* We could use addList recursively here, but sometimes we produce
* very deeply nested operators and run out of stack space, so we
* just unroll the recursion when possible.
*
* We assume nodes are left-recursive.
*/
private void unrollBinaryOperator(
Node n, int op, String opStr, Context context,
Context rhsContext, int leftPrecedence, int rightPrecedence) {
Node firstNonOperator = n.getFirstChild();
while (firstNonOperator.getType() == op) {
firstNonOperator = firstNonOperator.getFirstChild();
}
addExpr(firstNonOperator, leftPrecedence, context);
Node current = firstNonOperator;
do {
current = current.getParent();
cc.addOp(opStr, true);
addExpr(current.getFirstChild().getNext(), rightPrecedence, rhsContext);
} while (current != n);
}
static boolean isSimpleNumber(String s) {
int len = s.length();
if (len == 0) {
return false;
}
for (int index = 0; index < len; index++) {
char c = s.charAt(index);
if (c < '0' || c > '9') {
return false;
}
}
return len == 1 || s.charAt(0) != '0';
}
static double getSimpleNumber(String s) {
if (isSimpleNumber(s)) {
try {
long l = Long.parseLong(s);
if (l < NodeUtil.MAX_POSITIVE_INTEGER_NUMBER) {
return l;
}
} catch (NumberFormatException e) {
// The number was too long to parse. Fall through to NaN.
}
}
return Double.NaN;
}
/**
* @return Whether the name is an indirect eval.
*/
private boolean isIndirectEval(Node n) {
return n.isName() && "eval".equals(n.getString()) &&
!n.getBooleanProp(Node.DIRECT_EVAL);
}
/**
* Adds a block or expression, substituting a VOID with an empty
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>/*
* Copyright 2004 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.*;
/**
* Verifies that constants are only assigned a value once.
* e.g. var XX = 5;
* XX = 3; // error!
* XX++; // error!
*
*/
class ConstCheck extends AbstractPostOrderCallback
implements CompilerPass {
static final DiagnosticType CONST_REASSIGNED_VALUE_ERROR =
DiagnosticType.error(
"JSC_CONSTANT_REASSIGNED_VALUE_ERROR",
"constant {0} assigned a value more than once");
private final AbstractCompiler compiler;
private final Set<Scope.Var> initializedConstants;
/**
* Creates an instance.
*/
public ConstCheck(AbstractCompiler compiler) {
this.compiler = compiler;
this.initializedConstants = new HashSet<Scope.Var>();
}
@Override
public void process(Node externs, Node root) {
Preconditions.checkState(compiler.getLifeCycleStage().isNormalized());
NodeTraversal.traverse(compiler, root, this);
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.NAME:
if (parent != null &&
parent.isVar() &&
n.hasChildren()) {
String name = n.getString();
Scope.Var var =
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>Override
public RenameStrategy getRenameStrategy() {
return RenameStrategy.MAPPED;
}
}
private NameSupplier createNameSupplier(
RenameStrategy renameStrategy, BiMap<String, String> previousMappings) {
previousMappings = previousMappings != null ?
previousMappings :
ImmutableBiMap.<String, String>of();
if (renameStrategy == RenameStrategy.STABLE) {
return new StableNameSupplier();
} else if (generatePseudoNames) {
return new PseudoNameSuppier(renameStrategy);
} else {
return new ObfuscatedNameSuppier(renameStrategy, previousMappings);
}
}
private NameSupplier createNameSupplier(
RenameStrategy renameStrategy, RenamingMap mappings) {
Preconditions.checkState(renameStrategy == RenameStrategy.MAPPED);
return new MappedNameSupplier(mappings);
}
private class GatherGenerators extends AbstractPostOrderCallback {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
JSDocInfo doc = n.getJSDocInfo();
if (doc == null) {
return;
}
int numGeneratorAnnotations =
(doc.isConsistentIdGenerator() ? 1 : 0) +
(doc.isIdGenerator() ? 1 : 0) +
(doc.isStableIdGenerator() ? 1 : 0) +
(doc.isMappedIdGenerator() ? 1 : 0);
if (numGeneratorAnnotations == 0) {
return;
} else if (numGeneratorAnnotations > 1) {
compiler.report(t.makeError(n, CONFLICTING_GENERATOR_TYPE));
}
String name = null;
if (n.isAssign()) {
name = n.getFirstChild().getQualifiedName();
} else if (n.isVar()) {
name = n.getFirstChild().getString();
} else if (n.isFunction()){
name = n.getFirstChild().getString();
if (name.isEmpty()) {
return;
}
}
if (doc.isConsistentIdGenerator()) {
consistNameMap.put(name, Maps.<String, String>newLinkedHashMap());
nameGenerators.put(
name, createNameSupplier(
RenameStrategy.CONSISTENT, previousMap.get(
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>name)));
} else if (doc.isStableIdGenerator()) {
nameGenerators.put(
name, createNameSupplier(
RenameStrategy.STABLE, previousMap.get(name)));
} else if (doc.isIdGenerator()) {
nameGenerators.put(
name, createNameSupplier(
RenameStrategy.INCONSISTENT, previousMap.get(name)));
} else if (doc.isMappedIdGenerator()) {
NameSupplier supplier = nameGenerators.get(name);
if (supplier == null
|| supplier.getRenameStrategy() != RenameStrategy.MAPPED) {
compiler.report(t.makeError(n, MISSING_NAME_MAP_FOR_GENERATOR));
// skip registering the name in the list of Generators if there no
// mapping.
return;
}
} else {
throw new IllegalStateException("unexpected");
}
idGeneratorMaps.put(name, Maps.<String, String>newLinkedHashMap());
}
}
@Override
public void process(Node externs, Node root) {
NodeTraversal.traverse(compiler, root, new GatherGenerators());
if (!nameGenerators.isEmpty()) {
NodeTraversal.traverse(compiler, root, new ReplaceGenerators());
}
}
private class ReplaceGenerators extends AbstractPostOrderCallback {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (!n.isCall()) {
return;
}
String callName = n.getFirstChild().getQualifiedName();
NameSupplier nameGenerator = nameGenerators.get(callName);
if (nameGenerator == null) {
return;
}
if (!t.inGlobalScope() &&
nameGenerator.getRenameStrategy() == RenameStrategy.INCONSISTENT) {
// Warn about calls not in the global scope.
compiler.report(t.makeError(n, NON_GLOBAL_ID_GENERATOR_CALL));
return;
}
if (nameGenerator.getRenameStrategy() == RenameStrategy.INCONSISTENT) {
for (Node ancestor : n.getAncestors()) {
if (NodeUtil.isControlStructure(ancestor)) {
// Warn about conditional calls.
compiler.report(t.makeError(n, CONDITIONAL_ID_GENERATOR_CALL));
return;
}
}
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> }
Node arg = n.getFirstChild().getNext();
if (arg.isString()) {
String rename = getObfuscatedName(
arg, callName, nameGenerator, arg.getString());
parent.replaceChild(n, IR.string(rename));
compiler.reportCodeChange();
} else if (arg.isObjectLit()) {
for (Node key : arg.children()) {
String rename = getObfuscatedName(
key, callName, nameGenerator, key.getString());
key.setString(rename);
// Prevent standard renaming by marking the key as quoted.
key.putBooleanProp(Node.QUOTED_PROP, true);
}
arg.detachFromParent();
parent.replaceChild(n, arg);
compiler.reportCodeChange();
}
// TODO(user): Error on id not a string or object literal.
}
private String getObfuscatedName(
Node id, String callName, NameSupplier nameGenerator, String name) {
String rename = null;
Map<String, String> idGeneratorMap = idGeneratorMaps.get(callName);
String instanceId = getIdForGeneratorNode(
nameGenerator.getRenameStrategy() != RenameStrategy.INCONSISTENT, id);
if (nameGenerator.getRenameStrategy() == RenameStrategy.CONSISTENT) {
Map<String, String> entry = consistNameMap.get(callName);
rename = entry.get(instanceId);
if (rename == null) {
rename = nameGenerator.getName(instanceId, name);
entry.put(instanceId, rename);
}
} else {
rename = nameGenerator.getName(instanceId, name);
}
idGeneratorMap.put(rename, instanceId);
return rename;
}
}
/**
* @return The serialize map of generators and their ids and their
* replacements.
*/
public String getSerializedIdMappings() {
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, Map<String, String>> replacements :
idGeneratorMaps.entrySet()) {
if (!replacements.getValue().isEmpty()) {
sb.append("[");
sb.append(replacements.getKey());
sb.append("]\n\n");
for (Map.Entry<String, String> replacement :
replacements.getValue().entrySet()) {
sb.append
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> void process(Node externs, Node root) {
Callback callback = new CheckRequiresForConstructorsCallback();
new NodeTraversal(compiler, callback).traverseRoots(externs, root);
}
@Override
public void hotSwapScript(Node scriptRoot, Node originalRoot) {
Callback callback = new CheckRequiresForConstructorsCallback();
new NodeTraversal(compiler, callback).traverseWithScope(scriptRoot,
SyntacticScopeCreator.generateUntypedTopScope(compiler));
}
// Return true if the name is a class name (starts with an uppercase
// character, but is not in all caps).
private static boolean isClassName(String name) {
return (name != null && name.length() > 1
&& Character.isUpperCase(name.charAt(0))
&& !name.equals(name.toUpperCase()));
}
// Return the shortest prefix of the className that refers to a class,
// or null if no part refers to a class.
private static String getOutermostClassName(String className) {
for (String part : className.split("\\.")) {
if (isClassName(part)) {
return className.substring(0, className.indexOf(part) +
part.length());
}
}
return null;
}
/**
* This class "records" each constructor and goog.require visited and creates
* a warning for each new node without an appropriate goog.require node.
*
*/
private class CheckRequiresForConstructorsCallback implements Callback {
private final List<String> constructors = Lists.newArrayList();
private final List<String> requires = Lists.newArrayList();
private final List<Node> newNodes = Lists.newArrayList();
@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
return parent == null || !parent.isScript() ||
!t.getInput().isExtern();
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.ASSIGN:
case Token.VAR:
maybeAddConstructor(t, n);
break;
case Token.FUNCTION:
// Exclude function expressions.
if (NodeUtil.isStatement(n)) {
maybeAddConstructor(t, n);
}
break;
case Token.CALL:
visitCall
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>Node(n, parent);
break;
case Token.SCRIPT:
visitScriptNode(t);
break;
case Token.NEW:
visitNewNode(t, n);
}
}
private void visitScriptNode(NodeTraversal t) {
Set<String> classNames = Sets.newHashSet();
for (Node node : newNodes) {
String className = node.getFirstChild().getQualifiedName();
String outermostClassName = getOutermostClassName(className);
boolean notProvidedByConstructors =
(constructors == null || !constructors.contains(className));
boolean notProvidedByRequires =
(requires == null || (!requires.contains(className)
&& !requires.contains(outermostClassName)));
if (notProvidedByConstructors && notProvidedByRequires
&& !classNames.contains(className)) {
compiler.report(
t.makeError(node, level, MISSING_REQUIRE_WARNING, className));
classNames.add(className);
}
}
// for the next script, if there is one, we don't want the new, ctor, and
// require nodes to spill over.
this.newNodes.clear();
this.requires.clear();
this.constructors.clear();
}
private void visitCallNode(Node n, Node parent) {
String required = codingConvention.extractClassNameIfRequire(n, parent);
if (required != null) {
requires.add(required);
}
}
private void visitNewNode(NodeTraversal t, Node n) {
Node qNameNode = n.getFirstChild();
// If the ctor is something other than a qualified name, ignore it.
if (!qNameNode.isQualifiedName()) {
return;
}
// Grab the root ctor namespace.
Node nameNode = qNameNode;
for (; nameNode.hasChildren(); nameNode = nameNode.getFirstChild()) {}
// We only consider programmer-defined constructors that are
// global variables, or are defined on global variables.
if (!nameNode.isName()) {
return;
}
String name = nameNode.getString();
Scope.Var var = t.getScope().getVar(name);
if (var == null || var.isLocal() || var.isExtern()) {
return;
}
newNodes.
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>Convention.isConstant(variableName);
}
@Override public boolean isConstantKey(String keyName) {
return nextConvention.isConstantKey(keyName);
}
@Override
public boolean isValidEnumKey(String key) {
return nextConvention.isValidEnumKey(key);
}
@Override
public boolean isOptionalParameter(Node parameter) {
return nextConvention.isOptionalParameter(parameter);
}
@Override
public boolean isVarArgsParameter(Node parameter) {
return nextConvention.isVarArgsParameter(parameter);
}
@Override
public boolean isExported(String name, boolean local) {
return nextConvention.isExported(name, local);
}
@Override
public final boolean isExported(String name) {
return isExported(name, false) || isExported(name, true);
}
@Override
public boolean isPrivate(String name) {
return nextConvention.isPrivate(name);
}
@Override
public SubclassRelationship getClassesDefinedByCall(Node callNode) {
return nextConvention.getClassesDefinedByCall(callNode);
}
@Override
public boolean isSuperClassReference(String propertyName) {
return nextConvention.isSuperClassReference(propertyName);
}
@Override
public String extractClassNameIfProvide(Node node, Node parent) {
return nextConvention.extractClassNameIfProvide(node, parent);
}
@Override
public String extractClassNameIfRequire(Node node, Node parent) {
return nextConvention.extractClassNameIfRequire(node, parent);
}
@Override
public String getExportPropertyFunction() {
return nextConvention.getExportPropertyFunction();
}
@Override
public String getExportSymbolFunction() {
return nextConvention.getExportSymbolFunction();
}
@Override
public List<String> identifyTypeDeclarationCall(Node n) {
return nextConvention.identifyTypeDeclarationCall(n);
}
@Override
public void applySubclassRelationship(FunctionType parentCtor,
FunctionType childCtor, SubclassType type) {
nextConvention.applySubclassRelationship(
parentCtor, childCtor, type);
}
@Override
public String getAbstractMethodName() {
return nextConvention.getAbstractMethodName();
}
@Override
public String getSingletonGetterClassName(Node callNode) {
return nextConvention.
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>Convention.getObjectLiteralCast(callNode);
}
@Override
public Collection<String> getIndirectlyDeclaredProperties() {
return nextConvention.getIndirectlyDeclaredProperties();
}
}
/**
* The default coding convention.
* Should be at the bottom of all proxy chains.
*/
private static class DefaultCodingConvention implements CodingConvention {
private static final long serialVersionUID = 1L;
@Override
public boolean isConstant(String variableName) {
return false;
}
@Override
public boolean isConstantKey(String variableName) {
return false;
}
@Override
public boolean isValidEnumKey(String key) {
return key != null && key.length() > 0;
}
@Override
public boolean isOptionalParameter(Node parameter) {
// be as lax as possible, but this must be mutually exclusive from
// var_args parameters.
return false;
}
@Override
public boolean isVarArgsParameter(Node parameter) {
// be as lax as possible
return false;
}
@Override
public boolean isExported(String name, boolean local) {
return local && name.startsWith("$super");
}
@Override
public boolean isExported(String name) {
return isExported(name, false) || isExported(name, true);
}
@Override
public boolean isPrivate(String name) {
return false;
}
@Override
public SubclassRelationship getClassesDefinedByCall(Node callNode) {
return null;
}
@Override
public boolean isSuperClassReference(String propertyName) {
return false;
}
@Override
public String extractClassNameIfProvide(Node node, Node parent) {
String message = "only implemented in GoogleCodingConvention";
throw new UnsupportedOperationException(message);
}
@Override
public String extractClassNameIfRequire(Node node, Node parent) {
String message = "only implemented in GoogleCodingConvention";
throw new UnsupportedOperationException(message);
}
@Override
public String getExportPropertyFunction() {
return null;
}
@Override
public String getExportSymbolFunction() {
return null;
}
@Override
public List<String> identifyTypeDeclarationCall(Node n) {
return null;
}
@Override
public void applySubclassRelationship(FunctionType parentCtor,
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> code.append('\n');
lineIndex++;
lineLength = 0;
}
}
@Override
void maybeLineBreak() {
maybeCutLine();
}
/**
* This may start a new line if the current line is longer than the line
* length threshold.
*/
@Override
void maybeCutLine() {
if (lineLength > lineLengthThreshold) {
startNewLine();
}
}
@Override
void endLine() {
startNewLine();
}
@Override
void appendBlockStart() {
append(" {");
indent++;
}
@Override
void appendBlockEnd() {
endLine();
indent--;
append("}");
}
@Override
void listSeparator() {
add(", ");
maybeLineBreak();
}
@Override
void endFunction(boolean statementContext) {
super.endFunction(statementContext);
if (statementContext) {
startNewLine();
}
}
@Override
void beginCaseBody() {
super.beginCaseBody();
indent++;
endLine();
}
@Override
void endCaseBody() {
super.endCaseBody();
indent--;
endStatement();
}
@Override
void appendOp(String op, boolean binOp) {
if (binOp) {
if (getLastChar() != ' ' && op.charAt(0) != ',') {
append(" ");
}
append(op);
append(" ");
} else {
append(op);
}
}
/**
* If the body of a for loop or the then clause of an if statement has
* a single statement, should it be wrapped in a block?
* {@inheritDoc}
*/
@Override
boolean shouldPreserveExtraBlocks() {
// When pretty-printing, always place the statement in its own block
// so it is printed on a separate line. This allows breakpoints to be
// placed on the statement.
return true;
}
/**
* @return The TRY node for the specified CATCH node.
*/
private Node getTryForCatch(Node n) {
return n.getParent().getParent();
}
/**
* @return Whether the a line break should be added after the specified
* BLOCK.
*/
@Override
boolean breakAfterBlockFor(Node n, boolean isStatement
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>Context) {
Preconditions.checkState(n.isBlock());
Node parent = n.getParent();
if (parent != null) {
int type = parent.getType();
switch (type) {
case Token.DO:
// Don't break before 'while' in DO-WHILE statements.
return false;
case Token.FUNCTION:
// FUNCTIONs are handled separately, don't break here.
return false;
case Token.TRY:
// Don't break before catch
return n != parent.getFirstChild();
case Token.CATCH:
// Don't break before finally
return !NodeUtil.hasFinally(getTryForCatch(parent));
case Token.IF:
// Don't break before else
return n == parent.getLastChild();
}
}
return true;
}
@Override
void endFile() {
maybeEndStatement();
}
}
static class CompactCodePrinter
extends MappedCodePrinter {
// The CompactCodePrinter tries to emit just enough newlines to stop there
// being lines longer than the threshold. Since the output is going to be
// gzipped, it makes sense to try to make the newlines appear in similar
// contexts so that gzip can encode them for 'free'.
//
// This version tries to break the lines at 'preferred' places, which are
// between the top-level forms. This works because top-level forms tend to
// be more uniform than arbitrary legal contexts. Better compression would
// probably require explicit modeling of the gzip algorithm.
private final boolean lineBreak;
private final boolean preferLineBreakAtEndOfFile;
private int lineStartPosition = 0;
private int preferredBreakPosition = 0;
private int prevCutPosition = 0;
private int prevLineStartPosition = 0;
/**
* @param lineBreak break the lines a bit more aggressively
* @param lineLengthThreshold The length of a line after which we force
* a newline when possible.
* @param createSrcMap Whether to gather source position
* mapping information when printing.
* @param sourceMapDetailLevel A filter to control which nodes get mapped into
* the source map.
*/
private CompactCodePrinter(boolean lineBreak,
boolean preferLineBreakAtEndOfFile, int lineLengthThreshold,
boolean
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>FreeCode;
}
@Override
public void process(Node externs, Node root) {
NodeTraversal.traverse(compiler, root, this);
// Code with hidden side-effect code is common, for example
// accessing "el.offsetWidth" forces a reflow in browsers, to allow this
// will still allowing local dead code removal in general,
// protect the "side-effect free" code in the source.
//
if (protectSideEffectFreeCode) {
protectSideEffects();
}
}
@Override
public void hotSwapScript(Node scriptRoot, Node originalRoot) {
NodeTraversal.traverse(compiler, scriptRoot, this);
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
// VOID nodes appear when there are extra semicolons at the BLOCK level.
// I've been unable to think of any cases where this indicates a bug,
// and apparently some people like keeping these semicolons around,
// so we'll allow it.
if (n.isEmpty() ||
n.isComma()) {
return;
}
if (parent == null) {
return;
}
// Do not try to remove a block or an expr result. We already handle
// these cases when we visit the child, and the peephole passes will
// fix up the tree in more clever ways when these are removed.
if (n.isExprResult() || n.isBlock()) {
return;
}
// This no-op statement was there so that JSDoc information could
// be attached to the name. This check should not complain about it.
if (n.isQualifiedName() && n.getJSDocInfo() != null) {
return;
}
boolean isResultUsed = NodeUtil.isExpressionResultUsed(n);
boolean isSimpleOp = NodeUtil.isSimpleOperator(n);
if (!isResultUsed &&
(isSimpleOp || !NodeUtil.mayHaveSideEffects(n, t.getCompiler()))) {
String msg = "This code lacks side-effects. Is there a bug?";
if (n.isString()) {
msg = "Is there a missing '+' on the previous line?";
} else if (isSimpleOp) {
msg = "The result of the '"
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> + Token.name(n.getType()).toLowerCase() +
"' operator is not being used.";
}
t.getCompiler().report(
t.makeError(n, level, USELESS_CODE_ERROR, msg));
// TODO(johnlenz): determine if it is necessary to
// try to protect side-effect free statements as well.
if (!NodeUtil.isStatement(n)) {
problemNodes.add(n);
}
}
}
/**
* Protect side-effect free nodes by making them parameters
* to a extern function call. This call will be removed
* after all the optimizations passes have run.
*/
private void protectSideEffects() {
if (!problemNodes.isEmpty()) {
addExtern();
for (Node n : problemNodes) {
Node name = IR.name(PROTECTOR_FN).srcref(n);
name.putBooleanProp(Node.IS_CONSTANT_NAME, true);
Node replacement = IR.call(name).srcref(n);
replacement.putBooleanProp(Node.FREE_CALL, true);
n.getParent().replaceChild(n, replacement);
replacement.addChildToBack(n);
}
compiler.reportCodeChange();
}
}
private void addExtern() {
Node name = IR.name(PROTECTOR_FN);
name.putBooleanProp(Node.IS_CONSTANT_NAME, true);
Node var = IR.var(name);
// Add "@noalias" so we can strip the method when AliasExternals is enabled.
JSDocInfoBuilder builder = new JSDocInfoBuilder(false);
builder.recordNoAlias();
var.setJSDocInfo(builder.build(var));
CompilerInput input = compiler.getSynthesizedExternsInput();
input.getAstRoot(compiler).addChildrenToBack(var);
compiler.reportCodeChange();
}
/**
* Remove side-effect sync functions.
*/
static class StripProtection extends AbstractPostOrderCallback implements CompilerPass {
private final AbstractCompiler compiler;
StripProtection(AbstractCompiler compiler) {
this.compiler = compiler;
}
@Override
public void process(Node externs, Node root) {
NodeTraversal.traverse(compiler, root, this);
}
@Override
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isCall()) {
Node target = n.getFirstChild();
// TODO(johnlenz): add this to the coding convention
// so we can remove goog.reflect.sinkValue as well.
if (target.isName() && target.getString().equals(PROTECTOR_FN)) {
Node expr = n.getLastChild();
n.detachChildren();
parent.replaceChild(n, expr);
}
}
}
}
}
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>> second) {
if (first.isEmpty()) {
return second;
}
if (second.isEmpty()) {
return first;
}
ImmutableList.Builder<T> builder = ImmutableList.builder();
builder.addAll(first);
builder.addAll(second);
return builder.build();
}
boolean hasAnyTemplateTypesInternal() {
for (JSType templateValue : templateValues) {
if (templateValue.hasAnyTemplateTypes()) {
return true;
}
}
return false;
}
}
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> paths contain a return statement.
* May spuriously report that a return statement is missing.
*
* @return true if all paths return, converse not necessarily true
*/
private static boolean fastAllPathsReturnCheck(ControlFlowGraph<Node> cfg) {
for (DiGraphEdge<Node, Branch> s : cfg.getImplicitReturn().getInEdges()) {
if (!s.getSource().getValue().isReturn()) {
return false;
}
}
return true;
}
@Override
public void exitScope(NodeTraversal t) {
}
@Override
public boolean shouldTraverse(
NodeTraversal nodeTraversal, Node n, Node parent) {
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
}
/**
* Determines if the given scope should explicitly return. All functions
* with non-void or non-unknown return types must have explicit returns.
*
* Exception: Constructors which specifically specify a return type are
* used to allow invocation without requiring the "new" keyword. They
* have an implicit return type. See unit tests.
*
* @return If a return type is expected, returns it. Otherwise, returns null.
*/
private JSType explicitReturnExpected(Node scope) {
FunctionType scopeType = JSType.toMaybeFunctionType(scope.getJSType());
if (scopeType == null) {
return null;
}
if (isEmptyFunction(scope)) {
return null;
}
if (scopeType.isConstructor()) {
return null;
}
JSType returnType = scopeType.getReturnType();
if (returnType == null) {
return null;
}
if (!isVoidOrUnknown(returnType)) {
return returnType;
}
return null;
}
/**
* @return {@code true} if function represents a JavaScript function
* with an empty body
*/
private static boolean isEmptyFunction(Node function) {
return function.getChildCount() == 3 &&
!function.getFirstChild().getNext().getNext().hasChildren();
}
/**
* @return {@code true} if returnType is void, unknown, or a union
* containing void or unknown
*/
private boolean isVoidOrUnknown(JSType returnType) {
final JSType voidType =
compiler
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> new PrepareAnnotations());
}
if (root != null) {
NodeTraversal.traverse(
compiler, root, new PrepareAnnotations());
}
}
}
/**
* Covert EXPR_VOID to EXPR_RESULT to simplify the rest of the code.
*/
private void normalizeNodeTypes(Node n) {
normalizeBlocks(n);
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
// This pass is run during the CompilerTestCase validation, so this
// parent pointer check serves as a more general check.
Preconditions.checkState(child.getParent() == n);
normalizeNodeTypes(child);
}
}
/**
* Add blocks to IF, WHILE, DO, etc.
*/
private void normalizeBlocks(Node n) {
if (NodeUtil.isControlStructure(n)
&& !n.isLabel()
&& !n.isSwitch()) {
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (NodeUtil.isControlStructureCodeBlock(n, c) && !c.isBlock()) {
Node newBlock = IR.block().srcref(n);
n.replaceChild(c, newBlock);
if (!c.isEmpty()) {
newBlock.addChildrenToFront(c);
} else {
newBlock.setWasEmptyNode(true);
}
c = newBlock;
reportChange();
}
}
}
}
/**
* Normalize where annotations appear on the AST. Copies
* around existing JSDoc annotations as well as internal annotations.
*/
static class PrepareAnnotations
implements NodeTraversal.Callback {
PrepareAnnotations() {
}
@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.isObjectLit()) {
normalizeObjectLiteralAnnotations(n);
}
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.CALL:
annotateCalls(n);
break;
case Token.FUNCTION:
annotateDispatchers(n, parent);
break;
}
}
private void normalizeObjectLiteralAnnotations(Node objlit) {
Preconditions.checkState
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>(objlit.isObjectLit());
for (Node key = objlit.getFirstChild();
key != null; key = key.getNext()) {
Node value = key.getFirstChild();
normalizeObjectLiteralKeyAnnotations(objlit, key, value);
}
}
/**
* There are two types of calls we are interested in calls without explicit
* "this" values (what we are call "free" calls) and direct call to eval.
*/
private void annotateCalls(Node n) {
Preconditions.checkState(n.isCall());
// Keep track of of the "this" context of a call. A call without an
// explicit "this" is a free call.
Node first = n.getFirstChild();
// ignore cast nodes.
while (first.isCast()) {
first = first.getFirstChild();
}
if (!NodeUtil.isGet(first)) {
n.putBooleanProp(Node.FREE_CALL, true);
}
// Keep track of the context in which eval is called. It is important
// to distinguish between "(0, eval)()" and "eval()".
if (first.isName() &&
"eval".equals(first.getString())) {
first.putBooleanProp(Node.DIRECT_EVAL, true);
}
}
/**
* Translate dispatcher info into the property expected node.
*/
private void annotateDispatchers(Node n, Node parent) {
Preconditions.checkState(n.isFunction());
if (parent.getJSDocInfo() != null
&& parent.getJSDocInfo().isJavaDispatch()) {
if (parent.isAssign()) {
Preconditions.checkState(parent.getLastChild() == n);
n.putBooleanProp(Node.IS_DISPATCHER, true);
}
}
}
/**
* In the AST that Rhino gives us, it needs to make a distinction
* between JsDoc on the object literal node and JsDoc on the object literal
* value. For example,
* <pre>
* var x = {
* / JSDOC /
* a: 'b',
* c: / JSDOC / 'd'
* };
* </pre>
*
* But in few narrow cases (in particular, function literals), it's
* a
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>
declaredSuper =
declaredSuper.toMaybeTemplatizedType().getReferencedType();
}
if (declaredSuper != null &&
!(superObject instanceof UnknownType) &&
!declaredSuper.isEquivalentTo(superObject)) {
if (declaredSuper.isEquivalentTo(getNativeType(OBJECT_TYPE))) {
registerMismatch(superObject, declaredSuper, report(
t.makeError(n, MISSING_EXTENDS_TAG_WARNING, subObject.toString())));
} else {
mismatch(t.getSourceName(), n,
"mismatch in declaration of superclass type",
superObject, declaredSuper);
}
// Correct the super type.
if (!subCtor.hasCachedValues()) {
subCtor.setPrototypeBasedOn(superObject);
}
}
}
/**
* Expect that the first type can be cast to the second type. The first type
* must have some relationship with the second.
*
* @param t The node traversal.
* @param n The node where warnings should point.
* @param type The type being cast from.
* @param castType The type being cast to.
*/
void expectCanCast(NodeTraversal t, Node n, JSType castType, JSType type) {
if (!type.canCastTo(castType)) {
registerMismatch(type, castType, report(t.makeError(n, INVALID_CAST,
type.toString(), castType.toString())));
}
}
/**
* Expect that the given variable has not been declared with a type.
*
* @param sourceName The name of the source file we're in.
* @param n The node where warnings should point to.
* @param parent The parent of {@code n}.
* @param var The variable that we're checking.
* @param variableName The name of the variable.
* @param newType The type being applied to the variable. Mostly just here
* for the benefit of the warning.
* @return The variable we end up with. Most of the time, this will just
* be {@code var}, but in some rare cases we will need to declare
* a new var with new source info.
*/
Var expectUndeclaredVariable(String sourceName, CompilerInput input,
Node n, Node parent, Var
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> var, String variableName, JSType newType) {
Var newVar = var;
boolean allowDupe = false;
if (n.isGetProp() ||
NodeUtil.isObjectLitKey(n)) {
JSDocInfo info = n.getJSDocInfo();
if (info == null) {
info = parent.getJSDocInfo();
}
allowDupe =
info != null && info.getSuppressions().contains("duplicate");
}
JSType varType = var.getType();
// Only report duplicate declarations that have types. Other duplicates
// will be reported by the syntactic scope creator later in the
// compilation process.
if (varType != null &&
varType != typeRegistry.getNativeType(UNKNOWN_TYPE) &&
newType != null &&
newType != typeRegistry.getNativeType(UNKNOWN_TYPE)) {
// If there are two typed declarations of the same variable, that
// is an error and the second declaration is ignored, except in the
// case of native types. A null input type means that the declaration
// was made in TypedScopeCreator#createInitialScope and is a
// native type. We should redeclare it at the new input site.
if (var.input == null) {
Scope s = var.getScope();
s.undeclare(var);
newVar = s.declare(variableName, n, varType, input, false);
n.setJSType(varType);
if (parent.isVar()) {
if (n.getFirstChild() != null) {
n.getFirstChild().setJSType(varType);
}
} else {
Preconditions.checkState(parent.isFunction());
parent.setJSType(varType);
}
} else {
// Always warn about duplicates if the overridden type does not
// match the original type.
//
// If the types match, suppress the warning iff there was a @suppress
// tag, or if the original declaration was a stub.
if (!(allowDupe ||
var.getParentNode().isExprResult()) ||
!newType.isEquivalentTo(varType)) {
report(JSError.make(sourceName, n, DUP_VAR_DECLARATION,
variableName, newType.toString(), var.getInputName(),
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>
// Check for the typeof operator.
int operatorToken = condition.getType();
switch (operatorToken) {
case Token.EQ:
case Token.NE:
case Token.SHEQ:
case Token.SHNE:
case Token.CASE:
Node left;
Node right;
if (operatorToken == Token.CASE) {
left = condition.getParent().getFirstChild(); // the switch condition
right = condition.getFirstChild();
} else {
left = condition.getFirstChild();
right = condition.getLastChild();
}
Node typeOfNode = null;
Node stringNode = null;
if (left.isTypeOf() && right.isString()) {
typeOfNode = left;
stringNode = right;
} else if (right.isTypeOf() &&
left.isString()) {
typeOfNode = right;
stringNode = left;
}
if (typeOfNode != null && stringNode != null) {
Node operandNode = typeOfNode.getFirstChild();
JSType operandType = getTypeIfRefinable(operandNode, blindScope);
if (operandType != null) {
boolean resultEqualsValue = operatorToken == Token.EQ ||
operatorToken == Token.SHEQ || operatorToken == Token.CASE;
if (!outcome) {
resultEqualsValue = !resultEqualsValue;
}
return caseTypeOf(operandNode, operandType, stringNode.getString(),
resultEqualsValue, blindScope);
}
}
}
switch (operatorToken) {
case Token.AND:
if (outcome) {
return caseAndOrNotShortCircuiting(condition.getFirstChild(),
condition.getLastChild(), blindScope, true);
} else {
return caseAndOrMaybeShortCircuiting(condition.getFirstChild(),
condition.getLastChild(), blindScope, true);
}
case Token.OR:
if (!outcome) {
return caseAndOrNotShortCircuiting(condition.getFirstChild(),
condition.getLastChild(), blindScope, false);
} else {
return caseAndOrMaybeShortCircuiting(condition.getFirstChild(),
condition.getLastChild(), blindScope, false);
}
case Token.EQ:
if (outcome) {
return caseEquality(condition, blindScope, EQ);
} else {
return caseEquality(condition, blindScope, NE);
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> }
case Token.NE:
if (outcome) {
return caseEquality(condition, blindScope, NE);
} else {
return caseEquality(condition, blindScope, EQ);
}
case Token.SHEQ:
if (outcome) {
return caseEquality(condition, blindScope, SHEQ);
} else {
return caseEquality(condition, blindScope, SHNE);
}
case Token.SHNE:
if (outcome) {
return caseEquality(condition, blindScope, SHNE);
} else {
return caseEquality(condition, blindScope, SHEQ);
}
case Token.NAME:
case Token.GETPROP:
return caseNameOrGetProp(condition, blindScope, outcome);
case Token.ASSIGN:
return firstPreciserScopeKnowingConditionOutcome(
condition.getFirstChild(),
firstPreciserScopeKnowingConditionOutcome(
condition.getFirstChild().getNext(), blindScope, outcome),
outcome);
case Token.NOT:
return firstPreciserScopeKnowingConditionOutcome(
condition.getFirstChild(), blindScope, !outcome);
case Token.LE:
case Token.LT:
case Token.GE:
case Token.GT:
if (outcome) {
return caseEquality(condition, blindScope, ineq);
}
break;
case Token.INSTANCEOF:
return caseInstanceOf(
condition.getFirstChild(), condition.getLastChild(), blindScope,
outcome);
case Token.IN:
if (outcome && condition.getFirstChild().isString()) {
return caseIn(condition.getLastChild(),
condition.getFirstChild().getString(), blindScope);
}
break;
case Token.CASE:
Node left =
condition.getParent().getFirstChild(); // the switch condition
Node right = condition.getFirstChild();
if (outcome) {
return caseEquality(left, right, blindScope, SHEQ);
} else {
return caseEquality(left, right, blindScope, SHNE);
}
}
return nextPreciserScopeKnowingConditionOutcome(
condition, blindScope, outcome);
}
private FlowScope caseEquality(Node condition, FlowScope blindScope,
Function<TypePair, TypePair> merging) {
return caseEquality(condition.getFirstChild(), condition.getLastChild(),
blind
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>/*
* Copyright 2010 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.javascript.rhino.Node;
/**
* An abstract class whose implementations run peephole optimizations:
* optimizations that look at a small section of code and either remove
* that code (if it is not needed) or replaces it with smaller code.
*
*/
abstract class AbstractPeepholeOptimization {
private AbstractCompiler compiler;
/**
* Given a node to optimize and a traversal, optimize the node. Subclasses
* should override to provide their own peephole optimization.
*
* @param subtree The subtree that will be optimized.
* @return The new version of the subtree (or null if the subtree or one of
* its parents was removed from the AST). If the subtree has not changed,
* this method must return {@code subtree}.
*/
abstract Node optimizeSubtree(Node subtree);
/**
* Helper method for reporting an error to the compiler when applying a
* peephole optimization.
*
* @param diagnostic The error type
* @param n The node for which the error should be reported
*/
protected void report(DiagnosticType diagnostic, Node n) {
JSError error =
JSError.make(NodeUtil.getSourceName(n), n, diagnostic, n.toString());
compiler.report(error);
}
/**
* Helper method for telling the compiler that something has changed.
* Subclasses must call these if they have changed the AST.
*/
protected void reportCodeChange() {
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> Preconditions.checkNotNull(compiler);
compiler.reportCodeChange();
}
/**
* Are the nodes equal for the purpose of inlining?
* If type aware optimizations are on, type equality is checked.
*/
protected boolean areNodesEqualForInlining(Node n1, Node n2) {
/* Our implementation delegates to the compiler. We provide this
* method because we don't want to expose Compiler to PeepholeOptimizations.
*/
Preconditions.checkNotNull(compiler);
return compiler.areNodesEqualForInlining(n1, n2);
}
/**
* Is the current AST normalized? (e.g. has the Normalize pass been run
* and has the Denormalize pass not yet been run?)
*/
protected boolean isASTNormalized() {
Preconditions.checkNotNull(compiler);
return compiler.getLifeCycleStage().isNormalized();
}
/**
* Informs the optimization that a traversal will begin.
*/
void beginTraversal(AbstractCompiler compiler) {
this.compiler = compiler;
}
/**
* Informs the optimization that a traversal has completed.
* @param compiler The current compiler.
*/
void endTraversal(AbstractCompiler compiler) {
this.compiler = null;
}
// NodeUtil's mayEffectMutableState and mayHaveSideEffects need access to the
// compiler object, route them through here to give them access.
/**
* @return Whether the node may create new mutable state, or change existing
* state.
*/
boolean mayEffectMutableState(Node n) {
return NodeUtil.mayEffectMutableState(n, compiler);
}
/**
* @return Whether the node may have side effects when executed.
*/
boolean mayHaveSideEffects(Node n) {
return NodeUtil.mayHaveSideEffects(n, compiler);
}
/**
* Returns true if the current node's type implies side effects.
*
* This is a non-recursive version of the may have side effects
* check; used to check wherever the current node's type is one of
* the reason's why a subtree has side effects.
*/
boolean nodeTypeMayHaveSideEffects(Node n) {
return NodeUtil.nodeTypeMayHaveSideEffects(n, compiler);
}
/**
* @return Whether the source code version is ECMAScript
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>5 or later.
* Workarounds for quirks in browsers that do not support ES5 can be
* ignored when this is true.
*/
boolean isEcmaScript5OrGreater() {
return compiler != null
&& compiler.acceptEcmaScript5();
}
/**
* @return the current coding convention.
*/
CodingConvention getCodingConvention() {
// Note: this assumes a thread safe coding convention object.
return compiler.getCodingConvention();
}
/**
* Check if the specified node is null or is still in the AST.
*/
@VisibleForTesting
static Node validateResult(Node n) {
done: {
if (n != null && !n.isScript()
&& (!n.isBlock() || !n.isSyntheticBlock())) {
for (Node parent : n.getAncestors()) {
if (parent.isScript()) {
break done;
}
}
Preconditions.checkState(false);
}
}
return n;
}
}
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>_THIS = DiagnosticType.warning(
"JSC_USED_GLOBAL_THIS",
"dangerous use of the global 'this' object");
private final AbstractCompiler compiler;
/**
* If {@code assignLhsChild != null}, then the node being traversed is
* a descendant of the first child of an ASSIGN node. assignLhsChild's
* parent is this ASSIGN node.
*/
private Node assignLhsChild = null;
CheckGlobalThis(AbstractCompiler compiler) {
this.compiler = compiler;
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.isFunction()) {
// Don't traverse functions that are constructors or have the @this
// or @override annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING_KEY)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends != null &&
maybeLends.getLendsName() != null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
}
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> if (parent != null && parent.isAssign()) {
Node lhs = parent.getFirstChild();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
// Only traverse the right side if it's not an assignment to a prototype
// property or subproperty.
if (NodeUtil.isGet(lhs)) {
if (lhs.isGetProp() &&
lhs.getLastChild().getString().equals("prototype")) {
return false;
}
Node llhs = lhs.getFirstChild();
if (llhs.isGetProp() &&
llhs.getLastChild().getString().equals("prototype")) {
return false;
}
}
}
}
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isThis() && shouldReportThis(n)) {
compiler.report(t.makeError(n, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n) {
Node parent = n.getParent();
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
return parent != null && NodeUtil.isGet(parent);
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
* ... x = function() {};
* var ... x = function() {};
* ... var x = function() {};
* </pre>
*/
private JSDocInfo getFunctionJsDocInfo(Node n) {
JSDocInfo jsDoc = n.getJSDocInfo();
Node parent = n.getParent();
if (jsDoc == null) {
int parentType = parent.getType();
if
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> (parentType == Token.NAME || parentType == Token.ASSIGN) {
jsDoc = parent.getJSDocInfo();
if (jsDoc == null && parentType == Token.NAME) {
Node gramps = parent.getParent();
if (gramps.isVar()) {
jsDoc = gramps.getJSDocInfo();
}
}
}
}
return jsDoc;
}
}
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>
public void process(Node externs, Node root) {
if (namespace == null) {
namespace = new GlobalNamespace(compiler, root);
}
overrideDefines(collectDefines(root, namespace));
}
private void overrideDefines(Map<String, DefineInfo> allDefines) {
boolean changed = false;
for (Map.Entry<String, DefineInfo> def : allDefines.entrySet()) {
String defineName = def.getKey();
DefineInfo info = def.getValue();
Node inputValue = dominantReplacements.get(defineName);
Node finalValue = inputValue != null ?
inputValue : info.getLastValue();
if (finalValue != info.initialValue) {
info.initialValueParent.replaceChild(
info.initialValue, finalValue.cloneTree());
compiler.addToDebugLog("Overriding @define variable " + defineName);
changed = changed ||
finalValue.getType() != info.initialValue.getType() ||
!finalValue.isEquivalentTo(info.initialValue);
}
}
if (changed) {
compiler.reportCodeChange();
}
Set<String> unusedReplacements = dominantReplacements.keySet();
unusedReplacements.removeAll(allDefines.keySet());
unusedReplacements.removeAll(KNOWN_DEFINES);
for (String unknownDefine : unusedReplacements) {
compiler.report(JSError.make(UNKNOWN_DEFINE_WARNING, unknownDefine));
}
}
private static String format(MessageFormat format, Object... params) {
return format.format(params);
}
/**
* Only defines of literal number, string, or boolean are supported.
*/
private boolean isValidDefineType(JSTypeExpression expression) {
JSType type = expression.evaluate(null, compiler.getTypeRegistry());
return !type.isUnknownType() && type.isSubtype(
compiler.getTypeRegistry().getNativeType(
JSTypeNative.NUMBER_STRING_BOOLEAN));
}
/**
* Finds all defines, and creates a {@link DefineInfo} data structure for
* each one.
* @return A map of {@link DefineInfo} structures, keyed by name.
*/
private Map<String, DefineInfo> collectDefines(Node root,
GlobalNamespace namespace) {
// Find all the global names with a @define annotation
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> List<Name> allDefines = Lists.newArrayList();
for (Name name : namespace.getNameIndex().values()) {
Ref decl = name.getDeclaration();
if (name.docInfo != null && name.docInfo.isDefine()) {
// Process defines should not depend on check types being enabled,
// so we look for the JSDoc instead of the inferred type.
if (isValidDefineType(name.docInfo.getType())) {
allDefines.add(name);
} else {
JSError error = JSError.make(
decl.getSourceName(),
decl.node, INVALID_DEFINE_TYPE_ERROR);
compiler.report(error);
}
} else {
for (Ref ref : name.getRefs()) {
if (ref == decl) {
// Declarations were handled above.
continue;
}
Node n = ref.node;
Node parent = ref.node.getParent();
JSDocInfo info = n.getJSDocInfo();
if (info == null &&
parent.isVar() && parent.hasOneChild()) {
info = parent.getJSDocInfo();
}
if (info != null && info.isDefine()) {
allDefines.add(name);
break;
}
}
}
}
CollectDefines pass = new CollectDefines(compiler, allDefines);
NodeTraversal.traverse(compiler, root, pass);
return pass.getAllDefines();
}
/**
* Finds all assignments to @defines, and figures out the last value of
* the @define.
*/
private static final class CollectDefines implements Callback {
private final AbstractCompiler compiler;
private final Map<String, DefineInfo> assignableDefines;
private final Map<String, DefineInfo> allDefines;
private final Map<Node, RefInfo> allRefInfo;
// A hack that allows us to remove ASSIGN/VAR statements when
// we're currently visiting one of the children of the assign.
private Node lvalueToRemoveLater = null;
// A stack tied to the node traversal, to keep track of whether
// we're in a conditional block. If 1 is at the top, assignment to
// a define is allowed. Otherwise, it's not allowed.
private final Deque<Integer> assignAllowed;
CollectDefines(AbstractCompiler compiler, List<
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>Name> listOfDefines) {
this.compiler = compiler;
this.allDefines = Maps.newHashMap();
assignableDefines = Maps.newHashMap();
assignAllowed = new ArrayDeque<Integer>();
assignAllowed.push(1);
// Create a map of references to defines keyed by node for easy lookup
allRefInfo = Maps.newHashMap();
for (Name name : listOfDefines) {
Ref decl = name.getDeclaration();
if (decl != null) {
allRefInfo.put(decl.node,
new RefInfo(decl, name));
}
for (Ref ref : name.getRefs()) {
if (ref == decl) {
// Declarations were handled above.
continue;
}
// If there's a TWIN def, only put one of the twins in.
if (ref.getTwin() == null || !ref.getTwin().isSet()) {
allRefInfo.put(ref.node, new RefInfo(ref, name));
}
}
}
}
/**
* Get a map of {@link DefineInfo} structures, keyed by the name of
* the define.
*/
Map<String, DefineInfo> getAllDefines() {
return allDefines;
}
/**
* Keeps track of whether the traversal is in a conditional branch.
* We traverse all nodes of the parse tree.
*/
@Override
public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
updateAssignAllowedStack(n, true);
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
RefInfo refInfo = allRefInfo.get(n);
if (refInfo != null) {
Ref ref = refInfo.ref;
Name name = refInfo.name;
String fullName = name.getFullName();
switch (ref.type) {
case SET_FROM_GLOBAL:
case SET_FROM_LOCAL:
Node valParent = getValueParent(ref);
Node val = valParent.getLastChild();
if (valParent.isAssign() && name.isSimpleName() &&
name.getDeclaration() == ref) {
// For defines, it's an error if a simple name is assigned
// before it's declared
compiler.report(
t
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>.makeError(val, INVALID_DEFINE_INIT_ERROR, fullName));
} else if (processDefineAssignment(t, fullName, val, valParent)) {
// remove the assignment so that the variable is still declared,
// but no longer assigned to a value, e.g.,
// DEF_FOO = 5; // becomes "5;"
// We can't remove the ASSIGN/VAR when we're still visiting its
// children, so we'll have to come back later to remove it.
refInfo.name.removeRef(ref);
lvalueToRemoveLater = valParent;
}
break;
default:
if (t.inGlobalScope()) {
// Treat this as a reference to a define in the global scope.
// After this point, the define must not be reassigned,
// or it's an error.
DefineInfo info = assignableDefines.get(fullName);
if (info != null) {
setDefineInfoNotAssignable(info, t);
assignableDefines.remove(fullName);
}
}
break;
}
}
if (!t.inGlobalScope() &&
n.getJSDocInfo() != null && n.getJSDocInfo().isDefine()) {
// warn about @define annotations in local scopes
compiler.report(
t.makeError(n, NON_GLOBAL_DEFINE_INIT_ERROR, ""));
}
if (lvalueToRemoveLater == n) {
lvalueToRemoveLater = null;
if (n.isAssign()) {
Node last = n.getLastChild();
n.removeChild(last);
parent.replaceChild(n, last);
} else {
Preconditions.checkState(n.isName());
n.removeChild(n.getFirstChild());
}
compiler.reportCodeChange();
}
if (n.isCall()) {
if (t.inGlobalScope()) {
// If there's a function call in the global scope,
// we just say it's unsafe and freeze all the defines.
//
// NOTE(nicksantos): We could be a lot smarter here. For example,
// ReplaceOverriddenVars keeps a call graph of all functions and
// which functions/variables that they reference, and tries
// to statically determine which functions are "safe" and which
// are
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> First declaration of this define.
info = new DefineInfo(value, valueParent);
allDefines.put(name, info);
assignableDefines.put(name, info);
} else if (info.recordAssignment(value)) {
// The define was already initialized, but this is a safe
// re-assignment.
return true;
} else {
// The define was already initialized, and this is an unsafe
// re-assignment.
compiler.report(
t.makeError(valueParent, DEFINE_NOT_ASSIGNABLE_ERROR,
name, info.getReasonWhyNotAssignable()));
}
}
return false;
}
/**
* Gets the parent node of the value for any assignment to a Name.
* For example, in the assignment
* {@code var x = 3;}
* the parent would be the NAME node.
*/
private static Node getValueParent(Ref ref) {
// there are two types of declarations: VARs and ASSIGNs
return ref.node.getParent() != null &&
ref.node.getParent().isVar() ?
ref.node : ref.node.getParent();
}
/**
* Records the fact that because of the current node in the node traversal,
* the define can't ever be assigned again.
*
* @param info Represents the define variable.
* @param t The current traversal.
*/
private void setDefineInfoNotAssignable(DefineInfo info, NodeTraversal t) {
info.setNotAssignable(format(REASON_DEFINE_NOT_ASSIGNABLE,
t.getLineNumber(), t.getSourceName()));
}
/**
* A simple data structure for associating a Ref with the name
* that it references.
*/
private static class RefInfo {
final Ref ref;
final Name name;
RefInfo(Ref ref, Name name) {
this.ref = ref;
this.name = name;
}
}
}
/**
* A simple class for storing information about a define.
* Gathers the initial value, the last assigned value, and whether
* the define can be safely assigned a new value.
*/
private static final class DefineInfo {
public final Node initialValueParent;
public final Node initialValue;
private Node lastValue;
private boolean isAssignable;
private String reasonNotAssignable;
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>: elements are {@code null} until requested by
* {@link #getControlFlowGraph()}. Note that {@link ArrayDeque} does not allow
* {@code null} elements, so {@link LinkedList} is used instead.
*/
Deque<ControlFlowGraph<Node>> cfgs = new LinkedList<ControlFlowGraph<Node>>();
/** The current source file name */
private String sourceName;
/** The current input */
private InputId inputId;
/** The scope creator */
private ScopeCreator scopeCreator;
/** Possible callback for scope entry and exist **/
private ScopedCallback scopeCallback;
/** Callback for passes that iterate over a list of functions */
public interface FunctionCallback {
void visit(AbstractCompiler compiler, Node fnRoot);
}
/**
* Callback for tree-based traversals
*/
public interface Callback {
/**
* <p>Visits a node in pre order (before visiting its children) and decides
* whether this node's children should be traversed. If children are
* traversed, they will be visited by
* {@link #visit(NodeTraversal, Node, Node)} in postorder.</p>
* <p>Implementations can have side effects (e.g. modifying the parse
* tree).</p>
* @return whether the children of this node should be visited
*/
boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent);
/**
* <p>Visits a node in postorder (after its children have been visited).
* A node is visited only if all its parents should be traversed
* ({@link #shouldTraverse(NodeTraversal, Node, Node)}).</p>
* <p>Implementations can have side effects (e.g. modifying the parse
* tree).</p>
*/
void visit(NodeTraversal t, Node n, Node parent);
}
/**
* Callback that also knows about scope changes
*/
public interface ScopedCallback extends Callback {
/**
* Called immediately after entering a new scope. The new scope can
* be accessed through t.getScope()
*/
void enterScope(NodeTraversal t);
/**
* Called immediately before exiting a scope. The ending scope can
* be accessed through t.getScope()
*/
void exitScope(NodeTraversal t);
}
/**
* Abstract callback to
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> visit all nodes in postorder.
*/
public abstract static class AbstractPostOrderCallback implements Callback {
@Override
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
return true;
}
}
/** Abstract callback to visit all nodes in preorder. */
public abstract static class AbstractPreOrderCallback implements Callback {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {}
}
/**
* Abstract scoped callback to visit all nodes in postorder.
*/
public abstract static class AbstractScopedCallback
implements ScopedCallback {
@Override
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
return true;
}
@Override
public void enterScope(NodeTraversal t) {}
@Override
public void exitScope(NodeTraversal t) {}
}
/**
* Abstract callback to visit all nodes but not traverse into function
* bodies.
*/
public abstract static class AbstractShallowCallback implements Callback {
@Override
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
// We do want to traverse the name of a named function, but we don't
// want to traverse the arguments or body.
return parent == null || !parent.isFunction() ||
n == parent.getFirstChild();
}
}
/**
* Abstract callback to visit all structure and statement nodes but doesn't
* traverse into functions or expressions.
*/
public abstract static class AbstractShallowStatementCallback
implements Callback {
@Override
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
return parent == null || NodeUtil.isControlStructure(parent)
|| NodeUtil.isStatementBlock(parent);
}
}
/**
* Abstract callback to visit a pruned set of nodes.
*/
public abstract static class AbstractNodeTypePruningCallback
implements Callback {
private final Set<Integer> nodeTypes;
private final boolean include;
/**
* Creates an abstract pruned callback.
* @param nodeTypes the nodes to include in the traversal
*/
public AbstractNodeTypePruningCallback(Set<Integer> nodeTypes) {
this(nodeTypes, true);
}
/**
* Creates an abstract pruned callback.
* @param node
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>Types the nodes to include/exclude in the traversal
* @param include whether to include or exclude the nodes in the traversal
*/
public AbstractNodeTypePruningCallback(Set<Integer> nodeTypes,
boolean include) {
this.nodeTypes = nodeTypes;
this.include = include;
}
@Override
public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
return include == nodeTypes.contains(n.getType());
}
}
/**
* Creates a node traversal using the specified callback interface.
*/
public NodeTraversal(AbstractCompiler compiler, Callback cb) {
this(compiler, cb, new SyntacticScopeCreator(compiler));
}
/**
* Creates a node traversal using the specified callback interface
* and the scope creator.
*/
public NodeTraversal(AbstractCompiler compiler, Callback cb,
ScopeCreator scopeCreator) {
this.callback = cb;
if (cb instanceof ScopedCallback) {
this.scopeCallback = (ScopedCallback) cb;
}
this.compiler = compiler;
this.inputId = null;
this.sourceName = "";
this.scopeCreator = scopeCreator;
}
private void throwUnexpectedException(Exception unexpectedException) {
// If there's an unexpected exception, try to get the
// line number of the code that caused it.
String message = unexpectedException.getMessage();
// TODO(user): It is possible to get more information if curNode or
// its parent is missing. We still have the scope stack in which it is still
// very useful to find out at least which function caused the exception.
if (inputId != null) {
message =
unexpectedException.getMessage() + "\n" +
formatNodeContext("Node", curNode) +
(curNode == null ?
"" :
formatNodeContext("Parent", curNode.getParent()));
}
compiler.throwInternalError(message, unexpectedException);
}
private String formatNodeContext(String label, Node n) {
if (n == null) {
return " " + label + ": NULL";
}
return " " + label + "(" + n.toString(false, false, false) + "): "
+ formatNodePosition(n);
}
/**
* Traverses a parse tree recursively.
*/
public void
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> traverse(Node root) {
try {
inputId = NodeUtil.getInputId(root);
sourceName = "";
curNode = root;
pushScope(root);
// null parent ensures that the shallow callbacks will traverse root
traverseBranch(root, null);
popScope();
} catch (Exception unexpectedException) {
throwUnexpectedException(unexpectedException);
}
}
public void traverseRoots(Node ... roots) {
traverseRoots(Lists.newArrayList(roots));
}
public void traverseRoots(List<Node> roots) {
if (roots.isEmpty()) {
return;
}
try {
Node scopeRoot = roots.get(0).getParent();
Preconditions.checkState(scopeRoot != null);
inputId = NodeUtil.getInputId(scopeRoot);
sourceName = "";
curNode = scopeRoot;
pushScope(scopeRoot);
for (Node root : roots) {
Preconditions.checkState(root.getParent() == scopeRoot);
traverseBranch(root, scopeRoot);
}
popScope();
} catch (Exception unexpectedException) {
throwUnexpectedException(unexpectedException);
}
}
private static final String MISSING_SOURCE = "[source unknown]";
private String formatNodePosition(Node n) {
String sourceFileName = getBestSourceFileName(n);
if (sourceFileName == null) {
return MISSING_SOURCE + "\n";
}
int lineNumber = n.getLineno();
int columnNumber = n.getCharno();
String src = compiler.getSourceLine(sourceFileName, lineNumber);
if (src == null) {
src = MISSING_SOURCE;
}
return sourceFileName + ":" + lineNumber + ":" + columnNumber + "\n"
+ src + "\n";
}
/**
* Traverses a parse tree recursively with a scope, starting with the given
* root. This should only be used in the global scope. Otherwise, use
* {@link #traverseAtScope}.
*/
void traverseWithScope(Node root, Scope s) {
Preconditions.checkState(s.isGlobal());
inputId = null;
sourceName = "";
curNode = root;
pushScope(s);
traverseBranch(root, null);
popScope();
}
/**
* Traverses a parse tree recursively with a scope
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>, starting at that scope's
* root.
*/
void traverseAtScope(Scope s) {
Node n = s.getRootNode();
if (n.isFunction()) {
// We need to do some extra magic to make sure that the scope doesn't
// get re-created when we dive into the function.
if (inputId == null) {
inputId = NodeUtil.getInputId(n);
}
sourceName = getSourceName(n);
curNode = n;
pushScope(s);
Node args = n.getFirstChild().getNext();
Node body = args.getNext();
traverseBranch(args, n);
traverseBranch(body, n);
popScope();
} else {
traverseWithScope(n, s);
}
}
/**
* Traverses an inner node recursively with a refined scope. An inner node may
* be any node with a non {@code null} parent (i.e. all nodes except the
* root).
*
* @param node the node to traverse
* @param parent the node's parent, it may not be {@code null}
* @param refinedScope the refined scope of the scope currently at the top of
* the scope stack or in trivial cases that very scope or {@code null}
*/
protected void traverseInnerNode(Node node, Node parent, Scope refinedScope) {
Preconditions.checkNotNull(parent);
if (refinedScope != null && getScope() != refinedScope) {
curNode = node;
pushScope(refinedScope);
traverseBranch(node, parent);
popScope();
} else {
traverseBranch(node, parent);
}
}
public AbstractCompiler getCompiler() {
return compiler;
}
/**
* Gets the current line number, or zero if it cannot be determined. The line
* number is retrieved lazily as a running time optimization.
*/
public int getLineNumber() {
Node cur = curNode;
while (cur != null) {
int line = cur.getLineno();
if (line >= 0) {
return line;
}
cur = cur.getParent();
}
return 0;
}
/**
* Gets the current input source name.
*
* @return A string that may be empty,
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>.traverse(root);
}
/**
* Traverses a list of node trees.
*/
public static void traverseRoots(
AbstractCompiler compiler, List<Node> roots, Callback cb) {
NodeTraversal t = new NodeTraversal(compiler, cb);
t.traverseRoots(roots);
}
public static void traverseRoots(
AbstractCompiler compiler, Callback cb, Node ... roots) {
NodeTraversal t = new NodeTraversal(compiler, cb);
t.traverseRoots(roots);
}
/**
* Traverses a branch.
*/
private void traverseBranch(Node n, Node parent) {
int type = n.getType();
if (type == Token.SCRIPT) {
inputId = n.getInputId();
sourceName = getSourceName(n);
}
curNode = n;
if (!callback.shouldTraverse(this, n, parent)) {
return;
}
if (type == Token.FUNCTION) {
traverseFunction(n, parent);
} else {
for (Node child = n.getFirstChild(); child != null; ) {
// child could be replaced, in which case our child node
// would no longer point to the true next
Node next = child.getNext();
traverseBranch(child, n);
child = next;
}
}
curNode = n;
callback.visit(this, n, parent);
}
/** Traverses a function. */
private void traverseFunction(Node n, Node parent) {
Preconditions.checkState(n.getChildCount() == 3);
Preconditions.checkState(n.isFunction());
final Node fnName = n.getFirstChild();
boolean isFunctionExpression = (parent != null)
&& NodeUtil.isFunctionExpression(n);
if (!isFunctionExpression) {
// Functions declarations are in the scope containing the declaration.
traverseBranch(fnName, n);
}
curNode = n;
pushScope(n);
if (isFunctionExpression) {
// Function expression names are only accessible within the function
// scope.
traverseBranch(fnName, n);
}
final Node args = fnName.getNext();
final Node body = args.getNext();
// Args
traverseBranch(args, n);
// Body
Preconditions.checkState(body.getNext() == null && body.
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>
Node catchBody = block().copyInformationFrom(tryBody);
return new Node(Token.TRY, tryBody, catchBody, finallyBody);
}
public static Node tryCatch(Node tryBody, Node catchNode) {
Preconditions.checkState(tryBody.isBlock());
Preconditions.checkState(catchNode.isCatch());
Node catchBody = blockUnchecked(catchNode).copyInformationFrom(catchNode);
return new Node(Token.TRY, tryBody, catchBody);
}
public static Node tryCatchFinally(
Node tryBody, Node catchNode, Node finallyBody) {
Preconditions.checkState(finallyBody.isBlock());
Node tryNode = tryCatch(tryBody, catchNode);
tryNode.addChildToBack(finallyBody);
return tryNode;
}
public static Node catchNode(Node expr, Node body) {
Preconditions.checkState(expr.isName());
Preconditions.checkState(body.isBlock());
return new Node(Token.CATCH, expr, body);
}
public static Node breakNode() {
return new Node(Token.BREAK);
}
public static Node breakNode(Node name) {
// TODO(johnlenz): additional validation here.
Preconditions.checkState(name.isLabelName());
return new Node(Token.BREAK, name);
}
public static Node continueNode() {
return new Node(Token.CONTINUE);
}
public static Node continueNode(Node name) {
// TODO(johnlenz): additional validation here.
Preconditions.checkState(name.isLabelName());
return new Node(Token.CONTINUE, name);
}
//
public static Node call(Node target, Node ... args) {
Node call = new Node(Token.CALL, target);
for (Node arg : args) {
Preconditions.checkState(mayBeExpression(arg));
call.addChildToBack(arg);
}
return call;
}
public static Node newNode(Node target, Node ... args) {
Node newcall = new Node(Token.NEW, target);
for (Node arg : args) {
Preconditions.checkState(mayBeExpression(arg));
newcall.addChildToBack(arg);
}
return newcall;
}
public static Node name(String name) {
return Node.
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>newString(Token.NAME, name);
}
public static Node getprop(Node target, Node prop) {
Preconditions.checkState(mayBeExpression(target));
Preconditions.checkState(prop.isString());
return new Node(Token.GETPROP, target, prop);
}
public static Node getelem(Node target, Node elem) {
Preconditions.checkState(mayBeExpression(target));
Preconditions.checkState(mayBeExpression(elem));
return new Node(Token.GETELEM, target, elem);
}
public static Node assign(Node target, Node expr) {
Preconditions.checkState(isAssignmentTarget(target));
Preconditions.checkState(mayBeExpression(expr));
return new Node(Token.ASSIGN, target, expr);
}
public static Node hook(Node cond, Node trueval, Node falseval) {
Preconditions.checkState(mayBeExpression(cond));
Preconditions.checkState(mayBeExpression(trueval));
Preconditions.checkState(mayBeExpression(falseval));
return new Node(Token.HOOK, cond, trueval, falseval);
}
public static Node comma(Node expr1, Node expr2) {
return binaryOp(Token.COMMA, expr1, expr2);
}
public static Node and(Node expr1, Node expr2) {
return binaryOp(Token.AND, expr1, expr2);
}
public static Node or(Node expr1, Node expr2) {
return binaryOp(Token.OR, expr1, expr2);
}
public static Node not(Node expr1) {
return unaryOp(Token.NOT, expr1);
}
/**
* "=="
*/
public static Node eq(Node expr1, Node expr2) {
return binaryOp(Token.EQ, expr1, expr2);
}
/**
* "==="
*/
public static Node sheq(Node expr1, Node expr2) {
return binaryOp(Token.SHEQ, expr1, expr2);
}
public static Node voidNode(Node expr1) {
return unaryOp(Token.VOID, expr1);
}
public static Node neg(Node expr1) {
return unaryOp(Token.NEG, expr1);
}
public static Node pos(Node expr1)
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> * so make a best guess.
*/
private static boolean mayBeStatement(Node n) {
if (!mayBeStatementNoReturn(n)) {
return n.isReturn();
}
return true;
}
/**
* It isn't possible to always determine if a detached node is a expression,
* so make a best guess.
*/
private static boolean mayBeExpression(Node n) {
switch (n.getType()) {
case Token.FUNCTION:
// FUNCTION is used both in expression and statement
// contexts.
return true;
case Token.ADD:
case Token.AND:
case Token.ARRAYLIT:
case Token.ASSIGN:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.ASSIGN_URSH:
case Token.ASSIGN_ADD:
case Token.ASSIGN_SUB:
case Token.ASSIGN_MUL:
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
case Token.BITAND:
case Token.BITOR:
case Token.BITNOT:
case Token.BITXOR:
case Token.CALL:
case Token.CAST:
case Token.COMMA:
case Token.DEC:
case Token.DELPROP:
case Token.DIV:
case Token.EQ:
case Token.FALSE:
case Token.GE:
case Token.GETPROP:
case Token.GETELEM:
case Token.GT:
case Token.HOOK:
case Token.IN:
case Token.INC:
case Token.INSTANCEOF:
case Token.LE:
case Token.LSH:
case Token.LT:
case Token.MOD:
case Token.MUL:
case Token.NAME:
case Token.NE:
case Token.NEG:
case Token.NEW:
case Token.NOT:
case Token.NUMBER:
case Token.NULL:
case Token.OBJECTLIT:
case Token.OR:
case Token.POS:
case Token.REGEXP:
case Token.RSH:
case Token.SHEQ:
case Token.SHNE:
case Token.STRING:
case Token.SUB:
case Token.THIS:
case
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>.processForTesting(externsRoot, mainRoot);
}
hasCodeChanged = hasCodeChanged || recentChange.hasCodeChanged();
aggregateWarningCount += errorManagers[i].getWarningCount();
aggregateWarnings.addAll(Lists.newArrayList(compiler.getWarnings()));
if (normalizeEnabled) {
boolean verifyDeclaredConstants = true;
new Normalize.VerifyConstants(compiler, verifyDeclaredConstants)
.process(externsRoot, mainRoot);
}
}
}
if (error == null) {
assertEquals(
"Unexpected error(s): " + Joiner.on("\n").join(compiler.getErrors()),
0, compiler.getErrorCount());
// Verify the symbol table.
ErrorManager symbolTableErrorManager =
new BlackHoleErrorManager(compiler);
Node expectedRoot = null;
if (expected != null) {
expectedRoot = parseExpectedJs(expected);
expectedRoot.detachFromParent();
}
JSError[] stErrors = symbolTableErrorManager.getErrors();
if (expectedSymbolTableError != null) {
assertEquals("There should be one error.", 1, stErrors.length);
assertEquals(expectedSymbolTableError, stErrors[0].getType());
} else {
assertEquals("Unexpected symbol table error(s): " +
Joiner.on("\n").join(stErrors),
0, stErrors.length);
}
if (warning == null) {
assertEquals(
"Unexpected warning(s): " + Joiner.on("\n").join(aggregateWarnings),
0, aggregateWarningCount);
} else {
assertEquals("There should be one warning, repeated " + numRepetitions +
" time(s).", numRepetitions, aggregateWarningCount);
for (int i = 0; i < numRepetitions; ++i) {
JSError[] warnings = errorManagers[i].getWarnings();
JSError actual = warnings[0];
assertEquals(warning, actual.getType());
// Make sure that source information is always provided.
if (!allowSourcelessWarnings) {
assertTrue("Missing source file name in warning",
actual.sourceName != null && !actual.sourceName.isEmpty());
assertTrue("Missing line number in warning",
-1 != actual.lineNumber);
assertTrue("Missing char number in warning",
-1 != actual.get
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> compiler only; not accessed by compiler users.
protected boolean analyzeChangedScopesOnly = true;
// TODO(nicksantos): Decide if all of these are really necessary.
// Many of them are just accessors that should be passed to the
// CompilerPass's constructor.
/**
* Looks up an input (possibly an externs input) by input id.
* May return null.
*/
public abstract CompilerInput getInput(InputId inputId);
/**
* Looks up a source file by name. May return null.
*/
abstract SourceFile getSourceFileByName(String sourceName);
/**
* Creates a new externs file.
* @param name A name for the new externs file.
* @throws IllegalArgumentException If the name of the externs file conflicts
* with a pre-existing externs file.
*/
abstract CompilerInput newExternInput(String name);
/**
* Gets the module graph. May return null if there aren't at least two
* modules.
*/
abstract JSModuleGraph getModuleGraph();
/**
* Gets the inputs in the order in which they are being processed.
* Only for use by {@code AbstractCompilerRunner}.
*/
abstract List<CompilerInput> getInputsInOrder();
/**
* Gets a central registry of type information from the compiled JS.
*/
public abstract JSTypeRegistry getTypeRegistry();
/**
* Gets a memoized scope creator with type information.
*/
abstract ScopeCreator getTypedScopeCreator();
/**
* Gets the top scope.
*/
public abstract Scope getTopScope();
/**
* Report an error or warning.
*/
public abstract void report(JSError error);
/**
* Report an internal error.
*/
abstract void throwInternalError(String msg, Exception cause);
/**
* Gets the current coding convention.
*/
public abstract CodingConvention getCodingConvention();
/**
* Report code changes.
*
* Passes should call reportCodeChange when they alter the JS tree. This is
* verified by CompilerTestCase. This allows us to optimize to a fixed point.
*/
public abstract void reportCodeChange();
/**
* Logs a message under a central logger.
*/
abstract void addToDebugLog(String message);
/**
* Sets the CssRenamingMap.
*/
abstract void setCssRenamingMap(
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>/*
* Copyright 2004 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
/**
* Tests for {@link PeepholeSubstituteAlternateSyntax} in isolation.
* Tests for the interaction of multiple peephole passes are in
* PeepholeIntegrationTest.
*/
public class PeepholeSubstituteAlternateSyntaxTest extends CompilerTestCase {
// Externs for built-in constructors
// Needed for testFoldLiteralObjectConstructors(),
// testFoldLiteralArrayConstructors() and testFoldRegExp...()
private static final String FOLD_CONSTANTS_TEST_EXTERNS =
"var Object = function f(){};\n" +
"var RegExp = function f(a){};\n" +
"var Array = function f(a){};\n";
private boolean late = true;
// TODO(user): Remove this when we no longer need to do string comparison.
private PeepholeSubstituteAlternateSyntaxTest(boolean compareAsTree) {
super(FOLD_CONSTANTS_TEST_EXTERNS, compareAsTree);
}
public PeepholeSubstituteAlternateSyntaxTest() {
super(FOLD_CONSTANTS_TEST_EXTERNS);
}
@Override
public void setUp() throws Exception {
late = true;
super.setUp();
enableLineNumberCheck(true);
disableNormalize();
}
@Override
public CompilerPass getProcessor(final Compiler compiler) {
PeepholeOptimizationsPass peepholePass = new PeepholeOptimizationsPass(
compiler, new PeepholeSubstituteAlternateSyntax(late));
peepholePass.setRetraverseOnChange(false);
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>FoldReturnResult() {
foldSame("function f(){return !1;}");
foldSame("function f(){return null;}");
fold("function f(){return void 0;}",
"function f(){return}");
foldSame("function f(){return void foo();}");
fold("function f(){return undefined;}",
"function f(){return}");
fold("function f(){if(a()){return undefined;}}",
"function f(){if(a()){return}}");
}
public void testUndefined() {
foldSame("var x = undefined");
foldSame("function f(f) {var undefined=2;var x = undefined;}");
this.enableNormalize();
fold("var x = undefined", "var x=void 0");
foldSame(
"var undefined = 1;" +
"function f() {var undefined=2;var x = undefined;}");
foldSame("function f(undefined) {}");
foldSame("try {} catch(undefined) {}");
foldSame("for (undefined in {}) {}");
foldSame("undefined++;");
fold("undefined += undefined;", "undefined += void 0;");
}
public void testSplitCommaExpressions() {
late = false;
// Don't try to split in expressions.
foldSame("while (foo(), !0) boo()");
foldSame("var a = (foo(), !0);");
foldSame("a = (foo(), !0);");
// Don't try to split COMMA under LABELs.
foldSame("a:a(),b()");
fold("(x=2), foo()", "x=2; foo()");
fold("foo(), boo();", "foo(); boo()");
fold("(a(), b()), (c(), d());", "a(); b(); (c(), d());");
fold("a(); b(); (c(), d());", "a(); b(); c(); d();");
fold("foo(), true", "foo();true");
foldSame("foo();true");
fold("function x(){foo(), !0}", "function x(){foo(); !0}");
foldSame("function x(){foo(); !0}");
}
public void testComma1() {
late = false;
fold("1, 2", "1; 2");
late = true;
foldSame("1,
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>2");
}
public void testComma2() {
late = false;
test("1, a()", "1; a()");
late = true;
foldSame("1, a()");
}
public void testComma3() {
late = false;
test("1, a(), b()", "1; a(); b()");
late = true;
foldSame("1, a(), b()");
}
public void testComma4() {
late = false;
test("a(), b()", "a();b()");
late = true;
foldSame("a(), b()");
}
public void testComma5() {
late = false;
test("a(), b(), 1", "a();b();1");
late = true;
foldSame("a(), b(), 1");
}
public void testStringArraySplitting() {
testSame("var x=['1','2','3','4']");
testSame("var x=['1','2','3','4','5']");
test("var x=['1','2','3','4','5','6']",
"var x='123456'.split('')");
test("var x=['1','2','3','4','5','00']",
"var x='1 2 3 4 5 00'.split(' ')");
test("var x=['1','2','3','4','5','6','7']",
"var x='1234567'.split('')");
test("var x=['1','2','3','4','5','6','00']",
"var x='1 2 3 4 5 6 00'.split(' ')");
test("var x=[' ,',',',',',',',',',',']",
"var x=' ,;,;,;,;,;,'.split(';')");
test("var x=[',,',' ',',',',',',',',']",
"var x=',,; ;,;,;,;,'.split(';')");
test("var x=['a,',' ',',',',',',',',']",
"var x='a,; ;,;,;,;,'.split(';')");
// all possible delimiters used, leave it alone
testSame
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>(
SourceMapGeneratorFactory.getInstance(SourceMapFormat.V3));
}
};
abstract SourceMap getInstance();
}
/**
* Source maps can be very large different levels of detail can be specified.
*/
public static enum DetailLevel implements Predicate<Node> {
// ALL is best when the fullest details are needed for debugging or for
// code-origin analysis.
ALL {
@Override public boolean apply(Node node) {
return true;
}
},
// SYMBOLS is intended to be used for stack trace deobfuscation when full
// detail is not needed.
SYMBOLS {
@Override public boolean apply(Node node) {
return node.isCall()
|| node.isNew()
|| node.isFunction()
|| node.isName()
|| NodeUtil.isGet(node)
|| NodeUtil.isObjectLitKey(node)
|| (node.isString() && NodeUtil.isGet(node.getParent()));
}
};
}
public static class LocationMapping {
final String prefix;
final String replacement;
public LocationMapping(String prefix, String replacement) {
this.prefix = prefix;
this.replacement = replacement;
}
}
private final SourceMapGenerator generator;
private List<LocationMapping> prefixMappings = Collections.emptyList();
private final Map<String, String> sourceLocationFixupCache =
Maps.newHashMap();
private SourceMap(SourceMapGenerator generator) {
this.generator = generator;
}
public void addMapping(
Node node,
FilePosition outputStartPosition,
FilePosition outputEndPosition) {
String sourceFile = node.getSourceFileName();
// If the node does not have an associated source file or
// its line number is -1, then the node does not have sufficient
// information for a mapping to be useful.
if (sourceFile == null || node.getLineno() < 0) {
return;
}
sourceFile = fixupSourceLocation(sourceFile);
String originalName = (String) node.getProp(Node.ORIGINALNAME_PROP);
// Strangely, Rhino source lines are one based but columns are
// zero based.
// We don't change this for the v1 or v2 source maps but for
// v3 we make them both 0 based
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> "undefined-var errors" on
// variables that are defined in other JS files.
t.traverseWithScope(scriptRoot,
SyntacticScopeCreator.generateUntypedTopScope(compiler));
// TODO(bashir) Check if we need to createSynthesizedExternVar like process.
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (!n.isName()) {
return;
}
String varName = n.getString();
// Only a function can have an empty name.
if (varName.isEmpty()) {
Preconditions.checkState(parent.isFunction());
Preconditions.checkState(NodeUtil.isFunctionExpression(parent));
return;
}
// Check if this is a declaration for a var that has been declared
// elsewhere. If so, mark it as a duplicate.
if ((parent.isVar() ||
NodeUtil.isFunctionDeclaration(parent)) &&
varsToDeclareInExterns.contains(varName)) {
createSynthesizedExternVar(varName);
n.addSuppression("duplicate");
}
// Check that the var has been declared.
Scope scope = t.getScope();
Scope.Var var = scope.getVar(varName);
if (var == null) {
if (NodeUtil.isFunctionExpression(parent)) {
// e.g. [ function foo() {} ], it's okay if "foo" isn't defined in the
// current scope.
} else {
// The extern checks are stricter, don't report a second error.
if (!strictExternCheck || !t.getInput().isExtern()) {
t.report(n, UNDEFINED_VAR_ERROR, varName);
}
if (sanityCheck) {
throw new IllegalStateException("Unexpected variable " + varName);
} else {
createSynthesizedExternVar(varName);
scope.getGlobalScope().declare(varName, n,
null, compiler.getSynthesizedExternsInput());
}
}
return;
}
CompilerInput currInput = t.getInput();
CompilerInput varInput = var.input;
if (currInput == varInput || currInput == null || varInput == null) {
// The variable was defined in the same file. This is fine.
return;
}
// Check module dependencies.
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> JSModule currModule = currInput.getModule();
JSModule varModule = varInput.getModule();
JSModuleGraph moduleGraph = compiler.getModuleGraph();
if (!sanityCheck &&
varModule != currModule && varModule != null && currModule != null) {
if (moduleGraph.dependsOn(currModule, varModule)) {
// The module dependency was properly declared.
} else {
if (scope.isGlobal()) {
if (moduleGraph.dependsOn(varModule, currModule)) {
// The variable reference violates a declared module dependency.
t.report(n, VIOLATED_MODULE_DEP_ERROR,
currModule.getName(), varModule.getName(), varName);
} else {
// The variable reference is between two modules that have no
// dependency relationship. This should probably be considered an
// error, but just issue a warning for now.
t.report(n, MISSING_MODULE_DEP_ERROR,
currModule.getName(), varModule.getName(), varName);
}
} else {
t.report(n, STRICT_MODULE_DEP_ERROR,
currModule.getName(), varModule.getName(), varName);
}
}
}
}
/**
* Create a new variable in a synthetic script. This will prevent
* subsequent compiler passes from crashing.
*/
private void createSynthesizedExternVar(String varName) {
Node nameNode = IR.name(varName);
// Mark the variable as constant if it matches the coding convention
// for constant vars.
// NOTE(nicksantos): honestly, I'm not sure how much this matters.
// AFAIK, all people who use the CONST coding convention also
// compile with undeclaredVars as errors. We have some test
// cases for this configuration though, and it makes them happier.
if (compiler.getCodingConvention().isConstant(varName)) {
nameNode.putBooleanProp(Node.IS_CONSTANT_NAME, true);
}
getSynthesizedExternsRoot().addChildToBack(
IR.var(nameNode));
varsToDeclareInExterns.remove(varName);
compiler.reportCodeChange();
}
/**
* A check for name references in the externs inputs. These used to prevent
* a variable from getting renamed
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>, but no longer have any effect.
*/
private class NameRefInExternsCheck extends AbstractPostOrderCallback {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isName()) {
switch (parent.getType()) {
case Token.VAR:
case Token.FUNCTION:
case Token.PARAM_LIST:
// These are okay.
break;
case Token.GETPROP:
if (n == parent.getFirstChild()) {
Scope scope = t.getScope();
Scope.Var var = scope.getVar(n.getString());
if (var == null) {
t.report(n, UNDEFINED_EXTERN_VAR_ERROR, n.getString());
varsToDeclareInExterns.add(n.getString());
}
}
break;
default:
t.report(n, NAME_REFERENCE_IN_EXTERNS_ERROR, n.getString());
Scope scope = t.getScope();
Scope.Var var = scope.getVar(n.getString());
if (var == null) {
varsToDeclareInExterns.add(n.getString());
}
break;
}
}
}
}
/** Lazily create a "new" externs root for undeclared variables. */
private Node getSynthesizedExternsRoot() {
return compiler.getSynthesizedExternsInput().getAstRoot(compiler);
}
}
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> null;
}
key = n.getQualifiedName();
/*
* If it is not a simple variable and doesn't use this, then we assume
* global variable.
*/
Node base = getBase(n);
if (base != null && base.isThis()) {
if (base.getJSType().isUnknownType()) {
// Handle anonymous function created in constructor:
//
// /**
// * @extends {goog.SubDisposable}
// * @constructor */
// speel.Person = function() {
// this.run = function() {
// this.eh = new goog.events.EventHandler();
// }
//};
key = t.getScope().getParentScope().getTypeOfThis().toString() + "~"
+ key;
} else {
if (n.getFirstChild() == null) {
key = base.getJSType().toString() + "=" + key;
} else {
ObjectType objectType =
ObjectType.cast(dereference(n.getFirstChild().getJSType()));
if (objectType == null) {
return null;
}
ObjectType hObjT = objectType;
String propertyName = n.getLastChild().getString();
while (objectType != null) {
hObjT = objectType;
objectType = objectType.getImplicitPrototype();
if (objectType == null) {
break;
}
if (objectType.getDisplayName().endsWith("prototype")) {
continue;
}
if (!objectType.getPropertyNames().contains(propertyName)) {
break;
}
}
key = hObjT.toString() + "=" + key;
}
}
}
}
return key;
}
@Override
public void process(Node externs, Node root) {
// This pass should not have gotten added in this case
Preconditions.checkArgument(checkingPolicy != DisposalCheckingPolicy.OFF);
// Initialize types
googDisposableInterfaceType =
compiler.getTypeRegistry().getType(DISPOSABLE_INTERFACE_TYPE_NAME);
googEventsEventHandlerType = compiler.getTypeRegistry()
.getType(EVENT_HANDLER_TYPE_NAME);
/*
* Required types not found therefore the kind of pattern considered
* will not be found.
*/
if (googEventsEventHandlerType == null ||
googDisposableInterfaceType == null
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> true;
}
}
isConstructorStack.push(isConstructor);
isDisposalStack.push(isInDisposal);
} else {
isConstructorStack.push(inConstructorScope());
isDisposalStack.push(inDisposalScope());
}
}
@Override
public void exitScope(NodeTraversal t) {
isConstructorStack.pop();
isDisposalStack.pop();
}
/*
* Is the current node a call to goog.events.unlisten
*/
private void isGoogEventsUnlisten(Node n) {
Preconditions.checkArgument(n.getChildCount() > 3);
Node listener = n.getChildAtIndex(3);
Node objectWithListener = n.getChildAtIndex(1);
if (!objectWithListener.isQualifiedName()) {
return;
}
if (listener.isFunction()) {
/*
* Anonymous function
*/
compiler.report(JSError.make(n.getSourceFileName(), n,
UNLISTEN_WITH_ANONBOUND));
} else if (listener.isCall()) {
if (!listener.getFirstChild().isQualifiedName()) {
/*
* Anonymous function
*/
compiler.report(JSError.make(n.getSourceFileName(), n,
UNLISTEN_WITH_ANONBOUND));
} else if (listener.getFirstChild().getQualifiedName()
.equals("goog.bind")) {
/*
* Using goog.bind to unlisten
*/
compiler.report(JSError.make(n.getSourceFileName(), n,
UNLISTEN_WITH_ANONBOUND));
}
}
}
private void visitCall(NodeTraversal t, Node n) {
Node functionCalled = n.getFirstChild();
if (functionCalled == null ||
!functionCalled.isQualifiedName()) {
return;
}
String functionCalledName = functionCalled.getQualifiedName();
JSType typeOfThis = getTypeOfThisForScope(t);
if (typeOfThis == null) {
return;
}
/*
* Class considered eventful if there is an unlisten call in the
* disposal.
*/
if (functionCalledName.equals("goog.events.unlisten")) {
if (inDisposalScope()) {
eventfulTypes.add(typeOfThis);
}
isGoogEventsUn
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>listen(n);
}
if (inDisposalScope() &&
functionCalledName.equals("goog.events.removeAll")) {
eventfulTypes.add(typeOfThis);
}
/*
* If member with qualified name gets disposed of when this class
* gets disposed, consider the member type as an eventizer of this
* class.
*/
JSType disposedType = maybeReturnDisposedType(n, inDisposalScope());
if (!collectorFilterType(disposedType)) {
addEventize(getTypeOfThisForScope(t), disposedType);
}
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.CALL:
visitCall(t, n);
break;
default:
break;
}
}
}
private class Traversal extends AbstractPostOrderCallback
implements ScopedCallback {
/*
* Checks if the input node correspond to the creation of an eventful object
*/
private boolean createsEventfulObject(Node n) {
Node first = n.getFirstChild();
JSType type = n.getJSType();
if (first == null ||
!first.isQualifiedName() ||
type.isEmptyType() ||
type.isUnknownType()) {
return false;
}
boolean isOfTypeNeedingDisposal = false;
for (JSType disposableType : eventfulTypes) {
if (type.isSubtype(disposableType)) {
isOfTypeNeedingDisposal = true;
break;
}
}
return isOfTypeNeedingDisposal;
}
/*
* This function traverses the current scope to see if a locally
* defined eventful object is assigned to a live-out variable.
*
* Note: This function could be called multiple times to traverse
* the same scope if multiple local eventful objects are created in the
* scope.
*/
private Node localEventfulObjectAssign(
NodeTraversal t, Node propertyNode) {
Node parent;
if (!t.getScope().isGlobal()) {
/*
* In function
*/
parent = NodeUtil.getFunctionBody(t.getScopeRoot());
} else {
/*
* In global scope
*/
parent = t.getScopeRoot().getFirstChild();
}
/*
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>
* Check to see if locally created EventHandler is assigned to field
*/
for (Node sibling : parent.children()) {
if (sibling.isExprResult()) {
Node assign = sibling.getFirstChild();
if (assign.isAssign()) {
// assign.getLastChild().isEquivalentTo(propertyNode) did not work
if (propertyNode.getQualifiedName().equals(assign.getLastChild()
.getQualifiedName())) {
if (!assign.getFirstChild().isName()) {
return assign.getFirstChild();
}
}
}
}
}
/*
* Eventful object created and assigned to a local variable which is not
* assigned to another variable in a way to allow disposal.
*/
String key = generateKey(t, propertyNode, false);
if (key == null) {
return null;
}
EventfulObjectState e;
if (eventfulObjectMap.containsKey(key)) {
e = eventfulObjectMap.get(key);
if (e.seen == SeenType.ALLOCATED) {
e.seen = SeenType.ALLOCATED_LOCALLY;
}
} else {
e = new EventfulObjectState();
e.seen = SeenType.ALLOCATED_LOCALLY;
eventfulObjectMap.put(key, e);
}
e.allocationSite = propertyNode;
return null;
}
/*
* Record the creation of a new eventful object.
*/
private void visitNew(NodeTraversal t, Node n, Node parent) {
if (!createsEventfulObject(n)) {
return;
}
/*
* Insert allocation site and construct into eventfulObjectMap
*/
String key;
Node propertyNode;
/*
* Handles (E is an eventful class):
* - object.something = new E();
* - local = new E();
* - var local = new E();
*/
if (parent.isAssign()) {
propertyNode = parent.getFirstChild();
} else {
propertyNode = parent;
}
key = generateKey(t, propertyNode, false);
if (key == null) {
return;
}
EventfulObjectState e;
if (eventfulObjectMap.containsKey(key)) {
e = eventfulObjectMap.
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> }
}
}
}
private List<Node> maybeGetValueNodesFromCall(Node n) {
List<Node> ret = Lists.newArrayList();
Node first = n.getFirstChild();
if (first == null || !first.isQualifiedName()) {
return ret;
}
String property = first.getQualifiedName();
Node base = first.getFirstChild();
JSType baseType = null;
if (base != null) {
baseType = base.getJSType();
}
for (JSType key : disposeCalls.keySet()) {
if (key == null ||
(baseType != null && isPossiblySubtype(baseType, key))) {
addDisposeArgumentsMatched(disposeCalls.get(key), first, property,
ret);
}
}
return ret;
}
/*
* Look for calls to an eventful object's disposal functions.
* (dispose or removeAll will remove all event listeners from
* an EventHandler).
*/
private void visitCall(NodeTraversal t, Node n) {
// Filter the calls to find a "dispose" call
List<Node> variableNodes = maybeGetValueNodesFromCall(n);
for (Node variableNode : variableNodes) {
Preconditions.checkState(variableNode != null);
// Only consider removals on eventful object
boolean isTrackedRemoval = false;
JSType vnType = variableNode.getJSType();
for (JSType type : eventfulTypes) {
if (isPossiblySubtype(vnType, type)) {
isTrackedRemoval = true;
}
}
if (!isTrackedRemoval) {
continue;
}
String key = generateKey(t, variableNode, false);
if (key == null) {
continue;
}
eventfulObjectDisposed(t, variableNode);
}
}
/**
* Dereference a type, autoboxing it and filtering out null.
* From {@link CheckAccessControls}
*/
private JSType dereference(JSType type) {
return type == null ? null : type.dereference();
}
/*
* Check function definitions to add custom dispose methods.
*/
public void visitFunction(NodeTraversal t, Node n) {
Preconditions.checkArgument(n.isFunction());
JSDocInfo jsDocInfo = Node
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>FlowGraph();
LiveVariablesAnalysis liveness =
new LiveVariablesAnalysis(cfg, t.getScope(), compiler);
liveness.analyze();
for (Var v : liveness.getEscapedLocals()) {
eventfulObjectDisposed(t, v.getNode());
}
}
@Override
public void exitScope(NodeTraversal t) {
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.ASSIGN:
visitAssign(t, n);
break;
case Token.CALL:
visitCall(t, n);
break;
case Token.FUNCTION:
visitFunction(t, n);
break;
case Token.NEW:
visitNew(t, n, parent);
break;
case Token.RETURN:
visitReturn(t, n);
break;
default:
break;
}
}
}
}
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>.keySet();
}
@Override
public Scope getScope(Var var) {
return var.scope;
}
/**
* Gets the reference collection for the given variable.
*/
@Override
public ReferenceCollection getReferences(Var v) {
return referenceMap.get(v);
}
/**
* For each node, update the block stack and reference collection
* as appropriate.
*/
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isName()) {
Var v;
if (n.getString().equals("arguments")) {
v = t.getScope().getArgumentsVar();
} else {
v = t.getScope().getVar(n.getString());
}
if (v != null && varFilter.apply(v)) {
addReference(v, new Reference(n, t, blockStack.peek()));
}
}
if (isBlockBoundary(n, parent)) {
blockStack.pop();
}
}
/**
* Updates block stack and invokes any additional behavior.
*/
@Override
public void enterScope(NodeTraversal t) {
Node n = t.getScope().getRootNode();
BasicBlock parent = blockStack.isEmpty() ? null : blockStack.peek();
blockStack.push(new BasicBlock(parent, n));
}
/**
* Updates block stack and invokes any additional behavior.
*/
@Override
public void exitScope(NodeTraversal t) {
blockStack.pop();
if (t.getScope().isGlobal()) {
// Update global scope reference lists when we are done with it.
compiler.updateGlobalVarReferences(referenceMap, t.getScopeRoot());
behavior.afterExitScope(t, compiler.getGlobalVarReferences());
} else {
behavior.afterExitScope(t, new ReferenceMapWrapper(referenceMap));
}
}
/**
* Updates block stack.
*/
@Override
public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
// If node is a new basic block, put on basic block stack
if (isBlockBoundary(n, parent)) {
blockStack.push(new BasicBlock(blockStack.peek(), n));
}
return true;
}
/**
* @return true if
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> this node marks the start of a new basic block
*/
private static boolean isBlockBoundary(Node n, Node parent) {
if (parent != null) {
switch (parent.getType()) {
case Token.DO:
case Token.FOR:
case Token.TRY:
case Token.WHILE:
case Token.WITH:
// NOTE: TRY has up to 3 child blocks:
// TRY
// BLOCK
// BLOCK
// CATCH
// BLOCK
// Note that there is an explicit CATCH token but no explicit
// FINALLY token. For simplicity, we consider each BLOCK
// a separate basic BLOCK.
return true;
case Token.AND:
case Token.HOOK:
case Token.IF:
case Token.OR:
// The first child of a conditional is not a boundary,
// but all the rest of the children are.
return n != parent.getFirstChild();
}
}
return n.isCase();
}
private void addReference(Var v, Reference reference) {
// Create collection if none already
ReferenceCollection referenceInfo = referenceMap.get(v);
if (referenceInfo == null) {
referenceInfo = new ReferenceCollection();
referenceMap.put(v, referenceInfo);
}
// Add this particular reference
referenceInfo.add(reference);
}
interface ReferenceMap {
ReferenceCollection getReferences(Var var);
}
private static class ReferenceMapWrapper implements ReferenceMap {
private final Map<Var, ReferenceCollection> referenceMap;
public ReferenceMapWrapper(Map<Var, ReferenceCollection> referenceMap) {
this.referenceMap = referenceMap;
}
@Override
public ReferenceCollection getReferences(Var var) {
return referenceMap.get(var);
}
}
/**
* Way for callers to add specific behavior during traversal that
* utilizes the built-up reference information.
*/
interface Behavior {
/**
* Called after we finish with a scope.
*/
void afterExitScope(NodeTraversal t, ReferenceMap referenceMap);
}
static final Behavior DO_NOTHING_BEHAVIOR = new Behavior() {
@Override
public void afterExitScope(NodeTraversal t, ReferenceMap referenceMap) {}
};
/**
* A collection of references. Can be subclassed to apply checks or
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>
// Specifically, var declarations without assignments such as "var a;"
// are not.
return true;
}
return false;
}
/**
* @param index The index into the references array to look for an
* initialized assignment reference. That is, an assignment immediately
* follow a variable declaration that itself does not initialize the
* variable.
*/
private boolean isInitializingAssignmentAt(int index) {
if (index < references.size() && index > 0) {
Reference maybeDecl = references.get(index - 1);
if (maybeDecl.isVarDeclaration()) {
Preconditions.checkState(!maybeDecl.isInitializingDeclaration());
Reference maybeInit = references.get(index);
if (maybeInit.isSimpleAssignmentToName()) {
return true;
}
}
}
return false;
}
/**
* @return The reference that provides the value for the variable at the
* time of the first read, if known, otherwise null.
*
* This is either the variable declaration ("var a = ...") or first
* reference following the declaration if it is an assignment.
*/
Reference getInitializingReference() {
if (isInitializingDeclarationAt(0)) {
return references.get(0);
} else if (isInitializingAssignmentAt(1)) {
return references.get(1);
}
return null;
}
/**
* Constants are allowed to be defined after their first use.
*/
Reference getInitializingReferenceForConstants() {
int size = references.size();
for (int i = 0; i < size; i++) {
if (isInitializingDeclarationAt(i) || isInitializingAssignmentAt(i)) {
return references.get(i);
}
}
return null;
}
/**
* @return Whether the variable is only assigned a value once for its
* lifetime.
*/
boolean isAssignedOnceInLifetime() {
Reference ref = getOneAndOnlyAssignment();
if (ref == null) {
return false;
}
// Make sure this assignment is not in a loop.
for (BasicBlock block = ref.getBasicBlock();
block != null; block = block.getParent()) {
if (block.isFunction) {
if (ref.getSymbol().getScope() != ref.scope) {
return
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>.getScope(), t.getInput().getInputId());
}
/**
* Creates a variable reference in a given script file name, used in tests.
*
* @return The created reference.
*/
@VisibleForTesting
static Reference createRefForTest(CompilerInput input) {
return new Reference(new Node(Token.NAME), null, null,
input.getInputId());
}
private Reference(Node nameNode,
BasicBlock basicBlock, Scope scope, InputId inputId) {
this.nameNode = nameNode;
this.basicBlock = basicBlock;
this.scope = scope;
this.inputId = inputId;
this.sourceFile = nameNode.getStaticSourceFile();
}
/**
* Makes a copy of the current reference using a new Scope instance.
*/
Reference cloneWithNewScope(Scope newScope) {
return new Reference(nameNode, basicBlock, newScope, inputId);
}
@Override
public Var getSymbol() {
return scope.getVar(nameNode.getString());
}
@Override
public Node getNode() {
return nameNode;
}
public InputId getInputId() {
return inputId;
}
@Override
public StaticSourceFile getSourceFile() {
return sourceFile;
}
boolean isDeclaration() {
Node parent = getParent();
Node grandparent = parent.getParent();
return DECLARATION_PARENTS.contains(parent.getType()) ||
parent.isParamList() &&
grandparent.isFunction();
}
boolean isVarDeclaration() {
return getParent().isVar();
}
boolean isHoistedFunction() {
return NodeUtil.isHoistedFunctionDeclaration(getParent());
}
/**
* Determines whether the variable is initialized at the declaration.
*/
boolean isInitializingDeclaration() {
// VAR is the only type of variable declaration that may not initialize
// its variable. Catch blocks, named functions, and parameters all do.
return isDeclaration() &&
!getParent().isVar() ||
nameNode.getFirstChild() != null;
}
/**
* @return For an assignment, variable declaration, or function declaration
* return the assigned value, otherwise null.
*/
Node getAssignedValue() {
Node parent = getParent();
return (parent.isFunction())
? parent : NodeUtil.getAssignedValue
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>(nameNode);
}
BasicBlock getBasicBlock() {
return basicBlock;
}
Node getParent() {
return getNode().getParent();
}
Node getGrandparent() {
Node parent = getParent();
return parent == null ? null : parent.getParent();
}
private static boolean isLhsOfForInExpression(Node n) {
Node parent = n.getParent();
if (parent.isVar()) {
return isLhsOfForInExpression(parent);
}
return NodeUtil.isForIn(parent) && parent.getFirstChild() == n;
}
boolean isSimpleAssignmentToName() {
Node parent = getParent();
return parent.isAssign()
&& parent.getFirstChild() == nameNode;
}
boolean isLvalue() {
Node parent = getParent();
int parentType = parent.getType();
return (parentType == Token.VAR && nameNode.getFirstChild() != null)
|| parentType == Token.INC
|| parentType == Token.DEC
|| (NodeUtil.isAssignmentOp(parent)
&& parent.getFirstChild() == nameNode)
|| isLhsOfForInExpression(nameNode);
}
Scope getScope() {
return scope;
}
}
/**
* Represents a section of code that is uninterrupted by control structures
* (conditional or iterative logic).
*/
static final class BasicBlock {
private final BasicBlock parent;
/**
* Determines whether the block may not be part of the normal control flow,
* but instead "hoisted" to the top of the scope.
*/
private final boolean isHoisted;
/**
* Whether this block denotes a function scope.
*/
private final boolean isFunction;
/**
* Whether this block denotes a loop.
*/
private final boolean isLoop;
/**
* Creates a new block.
* @param parent The containing block.
* @param root The root node of the block.
*/
BasicBlock(BasicBlock parent, Node root) {
this.parent = parent;
// only named functions may be hoisted.
this.isHoisted = NodeUtil.isHoistedFunctionDeclaration(root);
this.isFunction = root.isFunction();
if (root.getParent() != null) {
int pType = root.getParent().getType
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>();
this.isLoop = pType == Token.DO ||
pType == Token.WHILE ||
pType == Token.FOR;
} else {
this.isLoop = false;
}
}
BasicBlock getParent() {
return parent;
}
/**
* Determines whether this block is equivalent to the very first block that
* is created when reference collection traversal enters global scope. Note
* that when traversing a single script in a hot-swap fashion a new instance
* of {@code BasicBlock} is created.
*
* @return true if this is global scope block.
*/
boolean isGlobalScopeBlock() {
return getParent() == null;
}
/**
* Determines whether this block is guaranteed to begin executing before
* the given block does.
*/
boolean provablyExecutesBefore(BasicBlock thatBlock) {
// If thatBlock is a descendant of this block, and there are no hoisted
// blocks between them, then this block must start before thatBlock.
BasicBlock currentBlock;
for (currentBlock = thatBlock;
currentBlock != null && currentBlock != this;
currentBlock = currentBlock.getParent()) {
if (currentBlock.isHoisted) {
return false;
}
}
if (currentBlock == this) {
return true;
}
if (isGlobalScopeBlock() && thatBlock.isGlobalScopeBlock()) {
return true;
}
return false;
}
}
}
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> suggestion;
this.distance = distance;
}
}
public TypeCheck(AbstractCompiler compiler,
ReverseAbstractInterpreter reverseInterpreter,
JSTypeRegistry typeRegistry,
Scope topScope,
MemoizedScopeCreator scopeCreator,
CheckLevel reportMissingOverride) {
this.compiler = compiler;
this.validator = compiler.getTypeValidator();
this.reverseInterpreter = reverseInterpreter;
this.typeRegistry = typeRegistry;
this.topScope = topScope;
this.scopeCreator = scopeCreator;
this.reportMissingOverride = reportMissingOverride;
this.reportUnknownTypes = ((Compiler) compiler).getOptions().enables(
DiagnosticGroups.REPORT_UNKNOWN_TYPES);
this.inferJSDocInfo = new InferJSDocInfo(compiler);
ClassLoader classLoader = TypeCheck.class.getClassLoader();
try {
Class<?> c = classLoader.loadClass(
"com.google.common.string.EditDistance");
editDistance = c.getDeclaredMethod(
"getEditDistance", String.class, String.class, boolean.class);
} catch (Exception ignored) {
editDistance = null;
}
}
public TypeCheck(AbstractCompiler compiler,
ReverseAbstractInterpreter reverseInterpreter,
JSTypeRegistry typeRegistry,
CheckLevel reportMissingOverride) {
this(compiler, reverseInterpreter, typeRegistry, null, null,
reportMissingOverride);
}
TypeCheck(AbstractCompiler compiler,
ReverseAbstractInterpreter reverseInterpreter,
JSTypeRegistry typeRegistry) {
this(compiler, reverseInterpreter, typeRegistry, null, null,
CheckLevel.WARNING);
}
/** Turn on the missing property check. Returns this for easy chaining. */
TypeCheck reportMissingProperties(boolean report) {
reportMissingProperties = report;
return this;
}
/**
* Main entry point for this phase of processing. This follows the pattern for
* JSCompiler phases.
*
* @param externsRoot The root of the externs parse tree.
* @param jsRoot The root of the input parse tree to be checked.
*/
@Override
public void process(Node externsRoot, Node jsRoot) {
Preconditions.checkNotNull(scopeCreator);
Preconditions.checkNotNull(topScope);
Node externsAndJs = jsRoot.getParent();
Preconditions.checkState(externsAndJs !=
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> null);
Preconditions.checkState(
externsRoot == null || externsAndJs.hasChild(externsRoot));
if (externsRoot != null) {
check(externsRoot, true);
}
check(jsRoot, false);
}
/** Main entry point of this phase for testing code. */
public Scope processForTesting(Node externsRoot, Node jsRoot) {
Preconditions.checkState(scopeCreator == null);
Preconditions.checkState(topScope == null);
Preconditions.checkState(jsRoot.getParent() != null);
Node externsAndJsRoot = jsRoot.getParent();
scopeCreator = new MemoizedScopeCreator(new TypedScopeCreator(compiler));
topScope = scopeCreator.createScope(externsAndJsRoot, null);
TypeInferencePass inference = new TypeInferencePass(compiler,
reverseInterpreter, topScope, scopeCreator);
inference.process(externsRoot, jsRoot);
process(externsRoot, jsRoot);
return topScope;
}
public void check(Node node, boolean externs) {
Preconditions.checkNotNull(node);
NodeTraversal t = new NodeTraversal(compiler, this, scopeCreator);
inExterns = externs;
t.traverseWithScope(node, topScope);
if (externs) {
inferJSDocInfo.process(node, null);
} else {
inferJSDocInfo.process(null, node);
}
}
private void checkNoTypeCheckSection(Node n, boolean enterSection) {
switch (n.getType()) {
case Token.SCRIPT:
case Token.BLOCK:
case Token.VAR:
case Token.FUNCTION:
case Token.ASSIGN:
JSDocInfo info = n.getJSDocInfo();
if (info != null && info.isNoTypeCheck()) {
if (enterSection) {
noTypeCheckSection++;
} else {
noTypeCheckSection--;
}
}
validator.setShouldReport(noTypeCheckSection == 0);
break;
}
}
private void report(NodeTraversal t, Node n, DiagnosticType diagnosticType,
String... arguments) {
if (noTypeCheckSection == 0) {
t.report(n, diagnosticType, arguments);
}
}
@
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>Override
public boolean shouldTraverse(
NodeTraversal t, Node n, Node parent) {
checkNoTypeCheckSection(n, true);
switch (n.getType()) {
case Token.FUNCTION:
// normal type checking
final Scope outerScope = t.getScope();
final String functionPrivateName = n.getFirstChild().getString();
if (functionPrivateName != null && functionPrivateName.length() > 0 &&
outerScope.isDeclared(functionPrivateName, false) &&
// Ideally, we would want to check whether the type in the scope
// differs from the type being defined, but then the extern
// redeclarations of built-in types generates spurious warnings.
!(outerScope.getVar(
functionPrivateName).getType() instanceof FunctionType)) {
report(t, n, FUNCTION_MASKS_VARIABLE, functionPrivateName);
}
// TODO(user): Only traverse the function's body. The function's
// name and arguments are traversed by the scope creator, and ideally
// should not be traversed by the type checker.
break;
}
return true;
}
/**
* This is the meat of the type checking. It is basically one big switch,
* with each case representing one type of parse tree node. The individual
* cases are usually pretty straightforward.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
* @param parent The parent of the node n.
*/
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
JSType childType;
JSType leftType, rightType;
Node left, right;
// To be explicitly set to false if the node is not typeable.
boolean typeable = true;
switch (n.getType()) {
case Token.CAST:
Node expr = n.getFirstChild();
JSType exprType = getJSType(expr);
JSType castType = getJSType(n);
// TODO(johnlenz): determine if we can limit object literals in some
// way.
if (!expr.isObjectLit()) {
validator.expectCanCast(t, n, castType, exprType);
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> }
ensureTyped(t, n, castType);
if (castType.isSubtype(exprType) || expr.isObjectLit()) {
expr.setJSType(castType);
}
break;
case Token.NAME:
typeable = visitName(t, n, parent);
break;
case Token.PARAM_LIST:
typeable = false;
break;
case Token.COMMA:
ensureTyped(t, n, getJSType(n.getLastChild()));
break;
case Token.TRUE:
case Token.FALSE:
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.THIS:
ensureTyped(t, n, t.getScope().getTypeOfThis());
break;
case Token.NULL:
ensureTyped(t, n, NULL_TYPE);
break;
case Token.NUMBER:
ensureTyped(t, n, NUMBER_TYPE);
break;
case Token.STRING:
ensureTyped(t, n, STRING_TYPE);
break;
case Token.STRING_KEY:
typeable = false;
break;
case Token.GETTER_DEF:
case Token.SETTER_DEF:
// Object literal keys are handled with OBJECTLIT
break;
case Token.ARRAYLIT:
ensureTyped(t, n, ARRAY_TYPE);
break;
case Token.REGEXP:
ensureTyped(t, n, REGEXP_TYPE);
break;
case Token.GETPROP:
visitGetProp(t, n, parent);
typeable = !(parent.isAssign() &&
parent.getFirstChild() == n);
break;
case Token.GETELEM:
visitGetElem(t, n);
// The type of GETELEM is always unknown, so no point counting that.
// If that unknown leaks elsewhere (say by an assignment to another
// variable), then it will be counted.
typeable = false;
break;
case Token.VAR:
visitVar(t, n);
typeable = false;
break;
case Token.NEW:
visitNew(t, n);
break;
case Token.CALL:
visitCall(t, n);
typeable = !parent.isExprResult();
break;
case Token.RETURN:
visitReturn(t, n
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>.IN:
left = n.getFirstChild();
right = n.getLastChild();
rightType = getJSType(right);
validator.expectString(t, left, getJSType(left), "left side of 'in'");
validator.expectObject(t, n, rightType, "'in' requires an object");
if (rightType.isStruct()) {
report(t, right, IN_USED_WITH_STRUCT);
}
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.INSTANCEOF:
left = n.getFirstChild();
right = n.getLastChild();
rightType = getJSType(right).restrictByNotNullOrUndefined();
validator.expectAnyObject(
t, left, getJSType(left), "deterministic instanceof yields false");
validator.expectActualObject(
t, right, rightType, "instanceof requires an object");
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.ASSIGN:
visitAssign(t, n);
typeable = false;
break;
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.ASSIGN_URSH:
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_SUB:
case Token.ASSIGN_ADD:
case Token.ASSIGN_MUL:
checkPropCreation(t, n.getFirstChild());
// fall through
case Token.LSH:
case Token.RSH:
case Token.URSH:
case Token.DIV:
case Token.MOD:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
case Token.SUB:
case Token.ADD:
case Token.MUL:
visitBinaryOperator(n.getType(), t, n);
break;
case Token.DELPROP:
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.CASE:
JSType switchType = getJSType(parent.getFirstChild());
JSType caseType = getJSType(n.getFirstChild());
validator.expectSwitchMatchesCase(t, n, switchType,
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> caseType);
typeable = false;
break;
case Token.WITH: {
Node child = n.getFirstChild();
childType = getJSType(child);
validator.expectObject(t, child, childType, "with requires an object");
typeable = false;
break;
}
case Token.FUNCTION:
visitFunction(t, n);
break;
// These nodes have no interesting type behavior.
case Token.LABEL:
case Token.LABEL_NAME:
case Token.SWITCH:
case Token.BREAK:
case Token.CATCH:
case Token.TRY:
case Token.SCRIPT:
case Token.EXPR_RESULT:
case Token.BLOCK:
case Token.EMPTY:
case Token.DEFAULT_CASE:
case Token.CONTINUE:
case Token.DEBUGGER:
case Token.THROW:
typeable = false;
break;
// These nodes require data flow analysis.
case Token.DO:
case Token.IF:
case Token.WHILE:
typeable = false;
break;
case Token.FOR:
if (NodeUtil.isForIn(n)) {
Node obj = n.getChildAtIndex(1);
if (getJSType(obj).isStruct()) {
report(t, obj, IN_USED_WITH_STRUCT);
}
}
typeable = false;
break;
// These nodes are typed during the type inference.
case Token.AND:
case Token.HOOK:
case Token.OBJECTLIT:
case Token.OR:
if (n.getJSType() != null) { // If we didn't run type inference.
ensureTyped(t, n);
} else {
// If this is an enum, then give that type to the objectlit as well.
if ((n.isObjectLit())
&& (parent.getJSType() instanceof EnumType)) {
ensureTyped(t, n, parent.getJSType());
} else {
ensureTyped(t, n);
}
}
if (n.isObjectLit()) {
JSType typ = getJSType(n);
for (Node key : n.children()) {
visitObjLitKey(t, key, n, typ);
}
}
break;
default:
report(t, n, UNEXPECTED_
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> } else if (!foundInterfaceProperty
&& !superClassHasProperty
&& !superInterfaceHasProperty) {
// there is no superclass nor interface implementation
compiler.report(
t.makeError(n, UNKNOWN_OVERRIDE,
propertyName, ctorType.getInstanceType().toString()));
}
}
/**
* Given a constructor or an interface type, find out whether the unknown
* type is a supertype of the current type.
*/
private static boolean hasUnknownOrEmptySupertype(FunctionType ctor) {
Preconditions.checkArgument(ctor.isConstructor() || ctor.isInterface());
Preconditions.checkArgument(!ctor.isUnknownType());
// The type system should notice inheritance cycles on its own
// and break the cycle.
while (true) {
ObjectType maybeSuperInstanceType =
ctor.getPrototype().getImplicitPrototype();
if (maybeSuperInstanceType == null) {
return false;
}
if (maybeSuperInstanceType.isUnknownType() ||
maybeSuperInstanceType.isEmptyType()) {
return true;
}
ctor = maybeSuperInstanceType.getConstructor();
if (ctor == null) {
return false;
}
Preconditions.checkState(ctor.isConstructor() || ctor.isInterface());
}
}
/**
* Visits an ASSIGN node for cases such as
* <pre>
* interface.property2.property = ...;
* </pre>
*/
private void visitInterfaceGetprop(NodeTraversal t, Node assign, Node object,
String property, Node lvalue, Node rvalue) {
JSType rvalueType = getJSType(rvalue);
// Only 2 values are allowed for methods:
// goog.abstractMethod
// function () {};
// or for properties, no assignment such as:
// InterfaceFoo.prototype.foobar;
String abstractMethodName =
compiler.getCodingConvention().getAbstractMethodName();
if (!rvalueType.isFunctionType()) {
// This is bad i18n style but we don't localize our compiler errors.
String abstractMethodMessage = (abstractMethodName != null)
? ", or " + abstractMethodName
: "";
compiler.report(
t.makeError(object, INVALID_INTERFACE_MEMBER_DECLARATION,
abstractMethodMessage));
}
if (assign.getLastChild().isFunction()
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> && !NodeUtil.isEmptyBlock(assign.getLastChild().getLastChild())) {
compiler.report(
t.makeError(object, INTERFACE_FUNCTION_NOT_EMPTY,
abstractMethodName));
}
}
/**
* Visits a NAME node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
* @param parent The parent of the node n.
* @return whether the node is typeable or not
*/
boolean visitName(NodeTraversal t, Node n, Node parent) {
// At this stage, we need to determine whether this is a leaf
// node in an expression (which therefore needs to have a type
// assigned for it) versus some other decorative node that we
// can safely ignore. Function names, arguments (children of LP nodes) and
// variable declarations are ignored.
// TODO(user): remove this short-circuiting in favor of a
// pre order traversal of the FUNCTION, CATCH, LP and VAR nodes.
int parentNodeType = parent.getType();
if (parentNodeType == Token.FUNCTION ||
parentNodeType == Token.CATCH ||
parentNodeType == Token.PARAM_LIST ||
parentNodeType == Token.VAR) {
return false;
}
JSType type = n.getJSType();
if (type == null) {
type = getNativeType(UNKNOWN_TYPE);
Var var = t.getScope().getVar(n.getString());
if (var != null) {
JSType varType = var.getType();
if (varType != null) {
type = varType;
}
}
}
ensureTyped(t, n, type);
return true;
}
/**
* Visits a GETPROP node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
* @param parent The parent of <code>n</code>
*/
private void visitGetProp(NodeTraversal t, Node n, Node parent) {
// obj.prop or obj.method()
// Lots of types can appear on the left
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> (pair.distance == shortest) {
if (bestSoFar != null &&
pair.suggestion.compareToIgnoreCase(bestSoFar) > 0) {
continue;
}
}
shortest = pair.distance;
bestSoFar = pair.suggestion;
}
}
}
}
if (bestSoFar != null) {
return new SuggestionPair(bestSoFar, shortest);
}
return null;
}
/**
* Determines whether this node is testing for the existence of a property.
* If true, we will not emit warnings about a missing property.
*
* @param getProp The GETPROP being tested.
*/
private boolean isPropertyTest(Node getProp) {
Node parent = getProp.getParent();
switch (parent.getType()) {
case Token.CALL:
return parent.getFirstChild() != getProp &&
compiler.getCodingConvention().isPropertyTestFunction(parent);
case Token.IF:
case Token.WHILE:
case Token.DO:
case Token.FOR:
return NodeUtil.getConditionExpression(parent) == getProp;
case Token.INSTANCEOF:
case Token.TYPEOF:
return true;
case Token.AND:
case Token.HOOK:
return parent.getFirstChild() == getProp;
case Token.NOT:
return parent.getParent().isOr() &&
parent.getParent().getFirstChild() == parent;
case Token.CAST:
return isPropertyTest(parent);
}
return false;
}
/**
* Visits a GETELEM node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
*/
private void visitGetElem(NodeTraversal t, Node n) {
validator.expectIndexMatch(
t, n, getJSType(n.getFirstChild()), getJSType(n.getLastChild()));
ensureTyped(t, n);
}
/**
* Visits a VAR node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
*/
private void
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> case Token.DIV:
case Token.MOD:
case Token.MUL:
case Token.SUB:
validator.expectNumber(t, left, leftType, "left operand");
validator.expectNumber(t, right, rightType, "right operand");
break;
case Token.ASSIGN_BITAND:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITOR:
case Token.BITAND:
case Token.BITXOR:
case Token.BITOR:
validator.expectBitwiseable(t, left, leftType,
"bad left operand to bitwise operator");
validator.expectBitwiseable(t, right, rightType,
"bad right operand to bitwise operator");
break;
case Token.ASSIGN_ADD:
case Token.ADD:
break;
default:
report(t, n, UNEXPECTED_TOKEN, Token.name(op));
}
ensureTyped(t, n);
}
/**
* <p>Checks enum aliases.
*
* <p>We verify that the enum element type of the enum used
* for initialization is a subtype of the enum element type of
* the enum the value is being copied in.</p>
*
* <p>Example:</p>
* <pre>var myEnum = myOtherEnum;</pre>
*
* <p>Enum aliases are irregular, so we need special code for this :(</p>
*
* @param value the value used for initialization of the enum
*/
private void checkEnumAlias(
NodeTraversal t, JSDocInfo declInfo, Node value) {
if (declInfo == null || !declInfo.hasEnumParameterType()) {
return;
}
JSType valueType = getJSType(value);
if (!valueType.isEnumType()) {
return;
}
EnumType valueEnumType = valueType.toMaybeEnumType();
JSType valueEnumPrimitiveType =
valueEnumType.getElementsType().getPrimitiveType();
validator.expectCanAssignTo(t, value, valueEnumPrimitiveType,
declInfo.getEnumParameterType().evaluate(t.getScope(), typeRegistry),
"incompatible enum element types");
}
/**
* This method gets the JSType from the Node argument and verifies that it is
* present.
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>rightContext",
"global", "ignoreCase", "lastIndex", "multiline", "source");
private final AbstractCompiler compiler;
private boolean globalRegExpPropertiesUsed = false;
public boolean isGlobalRegExpPropertiesUsed() {
return globalRegExpPropertiesUsed;
}
public CheckRegExp(AbstractCompiler compiler) {
this.compiler = compiler;
}
@Override
public void process(Node externs, Node root) {
NodeTraversal.traverse(compiler, root, this);
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isReferenceName(n)) {
String name = n.getString();
if (name.equals("RegExp") && t.getScope().getVar(name) == null) {
int parentType = parent.getType();
boolean first = (n == parent.getFirstChild());
if (!((parentType == Token.NEW && first)
|| (parentType == Token.CALL && first)
|| (parentType == Token.INSTANCEOF && !first)
|| parentType == Token.EQ || parentType == Token.NE
|| parentType == Token.SHEQ || parentType == Token.SHNE
|| parentType == Token.CASE
|| (parentType == Token.GETPROP && first
&& !REGEXP_PROPERTY_BLACKLIST.contains(
parent.getLastChild().getString())))) {
t.report(n, REGEXP_REFERENCE);
globalRegExpPropertiesUsed = true;
}
}
// Check the syntax of regular expression patterns.
} else if (n.isRegExp()) {
String pattern = n.getFirstChild().getString();
String flags = n.getChildCount() == 2
? n.getLastChild().getString() : "";
try {
RegExpTree.parseRegExp(pattern, flags);
} catch (IllegalArgumentException ex) {
t.report(n, MALFORMED_REGEXP, ex.getMessage());
} catch (IndexOutOfBoundsException ex) {
t.report(n, MALFORMED_REGEXP, ex.getMessage());
}
}
}
}
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>.hasType()) {
boolean valid = false;
switch (node.getType()) {
// Casts are valid
case com.google.javascript.rhino.head.Token.LP:
valid = node instanceof ParenthesizedExpression;
break;
// Variable declarations are valid
case com.google.javascript.rhino.head.Token.VAR:
valid = true;
break;
// Function declarations are valid
case com.google.javascript.rhino.head.Token.FUNCTION:
FunctionNode fnNode = (FunctionNode) node;
valid = fnNode.getFunctionType() == FunctionNode.FUNCTION_STATEMENT;
break;
// Object literal properties, catch declarations and variable
// initializers are valid.
case com.google.javascript.rhino.head.Token.NAME:
AstNode parent = node.getParent();
valid = parent instanceof ObjectProperty
|| parent instanceof CatchClause
|| parent instanceof FunctionNode
|| (parent instanceof VariableInitializer &&
node == ((VariableInitializer) parent).getTarget());
break;
// Object literal properties are valid
case com.google.javascript.rhino.head.Token.GET:
case com.google.javascript.rhino.head.Token.SET:
case com.google.javascript.rhino.head.Token.NUMBER:
case com.google.javascript.rhino.head.Token.STRING:
valid = node.getParent() instanceof ObjectProperty;
break;
// Property assignments are valid, if at the root of an expression.
case com.google.javascript.rhino.head.Token.ASSIGN:
if (node instanceof Assignment) {
valid = isExprStmt(node.getParent())
&& isPropAccess(((Assignment) node).getLeft());
}
break;
// Property definitions are valid, if at the root of an expression.
case com.google.javascript.rhino.head.Token.GETPROP:
case com.google.javascript.rhino.head.Token.GETELEM:
valid = isExprStmt(node.getParent());
break;
case com.google.javascript.rhino.head.Token.CALL:
valid = info.isDefine();
break;
}
if (!valid) {
errorReporter.warning(MISPLACED_TYPE_ANNOTATION,
sourceName,
node.get
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> {
reportGetterParam(el.getLeft());
}
} else if (el.isSetter()) {
key.setType(Token.SETTER_DEF);
Preconditions.checkState(value.isFunction());
if (!getFnParamNode(value).hasOneChild()) {
reportSetterParam(el.getLeft());
}
}
key.addChildToFront(value);
node.addChildToBack(key);
}
return node;
}
/**
* @param fnNode The function.
* @return The Node containing the Function parameters.
*/
Node getFnParamNode(Node fnNode) {
// Function NODE: [ FUNCTION -> NAME, LP -> ARG1, ARG2, ... ]
Preconditions.checkArgument(fnNode.isFunction());
return fnNode.getFirstChild().getNext();
}
@Override
Node processObjectProperty(ObjectProperty propertyNode) {
return processInfixExpression(propertyNode);
}
@Override
Node processParenthesizedExpression(ParenthesizedExpression exprNode) {
Node node = transform(exprNode.getExpression());
return node;
}
@Override
Node processPropertyGet(PropertyGet getNode) {
Node leftChild = transform(getNode.getTarget());
AstNode nodeProp = getNode.getProperty();
Node rightChild = transformAsString(nodeProp);
if (nodeProp instanceof Name && !isAllowedProp(
((Name) nodeProp).getIdentifier())) {
errorReporter.warning(INVALID_ES3_PROP_NAME, sourceName,
rightChild.getLineno(), "", rightChild.getCharno());
}
Node newNode = newNode(
Token.GETPROP, leftChild, rightChild);
newNode.setLineno(leftChild.getLineno());
newNode.setCharno(leftChild.getCharno());
maybeSetLengthFrom(newNode, getNode);
return newNode;
}
@Override
Node processRegExpLiteral(RegExpLiteral literalNode) {
Node literalStringNode = newStringNode(literalNode.getValue());
// assume it's on the same line.
literalStringNode.setLineno(literalNode.getLineno());
maybeSetLengthFrom(literalStringNode, literalNode);
Node node = newNode(Token.REGEXP, literalStringNode);
String flags = literalNode.getFlags();
if (flags != null && !
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>) {
switch (token) {
case com.google.javascript.rhino.head.Token.RETURN:
return Token.RETURN;
case com.google.javascript.rhino.head.Token.BITOR:
return Token.BITOR;
case com.google.javascript.rhino.head.Token.BITXOR:
return Token.BITXOR;
case com.google.javascript.rhino.head.Token.BITAND:
return Token.BITAND;
case com.google.javascript.rhino.head.Token.EQ:
return Token.EQ;
case com.google.javascript.rhino.head.Token.NE:
return Token.NE;
case com.google.javascript.rhino.head.Token.LT:
return Token.LT;
case com.google.javascript.rhino.head.Token.LE:
return Token.LE;
case com.google.javascript.rhino.head.Token.GT:
return Token.GT;
case com.google.javascript.rhino.head.Token.GE:
return Token.GE;
case com.google.javascript.rhino.head.Token.LSH:
return Token.LSH;
case com.google.javascript.rhino.head.Token.RSH:
return Token.RSH;
case com.google.javascript.rhino.head.Token.URSH:
return Token.URSH;
case com.google.javascript.rhino.head.Token.ADD:
return Token.ADD;
case com.google.javascript.rhino.head.Token.SUB:
return Token.SUB;
case com.google.javascript.rhino.head.Token.MUL:
return Token.MUL;
case com.google.javascript.rhino.head.Token.DIV:
return Token.DIV;
case com.google.javascript.rhino.head.Token.MOD:
return Token.MOD;
case com.google.javascript.rhino.head.Token.NOT:
return Token.NOT;
case com.google.javascript.rhino.head.Token.BITNOT:
return Token.BITNOT;
case com.google.javascript.rhino.head.Token.POS:
return Token.POS;
case com.
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>.Token.OBJECTLIT:
return Token.OBJECTLIT;
case com.google.javascript.rhino.head.Token.TRY:
return Token.TRY;
// The LP represents a parameter list
case com.google.javascript.rhino.head.Token.LP:
return Token.PARAM_LIST;
case com.google.javascript.rhino.head.Token.COMMA:
return Token.COMMA;
case com.google.javascript.rhino.head.Token.ASSIGN:
return Token.ASSIGN;
case com.google.javascript.rhino.head.Token.ASSIGN_BITOR:
return Token.ASSIGN_BITOR;
case com.google.javascript.rhino.head.Token.ASSIGN_BITXOR:
return Token.ASSIGN_BITXOR;
case com.google.javascript.rhino.head.Token.ASSIGN_BITAND:
return Token.ASSIGN_BITAND;
case com.google.javascript.rhino.head.Token.ASSIGN_LSH:
return Token.ASSIGN_LSH;
case com.google.javascript.rhino.head.Token.ASSIGN_RSH:
return Token.ASSIGN_RSH;
case com.google.javascript.rhino.head.Token.ASSIGN_URSH:
return Token.ASSIGN_URSH;
case com.google.javascript.rhino.head.Token.ASSIGN_ADD:
return Token.ASSIGN_ADD;
case com.google.javascript.rhino.head.Token.ASSIGN_SUB:
return Token.ASSIGN_SUB;
case com.google.javascript.rhino.head.Token.ASSIGN_MUL:
return Token.ASSIGN_MUL;
case com.google.javascript.rhino.head.Token.ASSIGN_DIV:
return Token.ASSIGN_DIV;
case com.google.javascript.rhino.head.Token.ASSIGN_MOD:
return Token.ASSIGN_MOD;
case com.google.javascript.rhino.head.Token.HOOK:
return Token.HOOK;
case com.google.javascript.rhino.head.Token.OR:
return Token.OR;
case com.google.javascript.rhino.head.Token.AND:
return Token.AND;
case com.google.javascript.rhino.head.Token.INC
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>().iterator().next());
}
symbols.add(symbols.get(0));
return Joiner.on(" -> ").join(symbols);
}
public List<INPUT> getSortedList() {
return Collections.<INPUT>unmodifiableList(sortedList);
}
/**
* Gets all the dependencies of the given roots. The inputs must be returned
* in a stable order. In other words, if A comes before B, and A does not
* transitively depend on B, then A must also come before B in the returned
* list.
*/
public List<INPUT> getSortedDependenciesOf(List<INPUT> roots) {
return getDependenciesOf(roots, true);
}
/**
* Gets all the dependencies of the given roots. The inputs must be returned
* in a stable order. In other words, if A comes before B, and A does not
* transitively depend on B, then A must also come before B in the returned
* list.
*
* @param sorted If true, get them in topologically sorted order. If false,
* get them in the original order they were passed to the compiler.
*/
public List<INPUT> getDependenciesOf(List<INPUT> roots, boolean sorted) {
Preconditions.checkArgument(inputs.containsAll(roots));
Set<INPUT> included = Sets.newHashSet();
Deque<INPUT> worklist = new ArrayDeque<INPUT>(roots);
while (!worklist.isEmpty()) {
INPUT current = worklist.pop();
if (included.add(current)) {
for (String req : current.getRequires()) {
INPUT dep = provideMap.get(req);
if (dep != null) {
worklist.add(dep);
}
}
}
}
ImmutableList.Builder<INPUT> builder = ImmutableList.builder();
for (INPUT current : (sorted ? sortedList : inputs)) {
if (included.contains(current)) {
builder.add(current);
}
}
return builder.build();
}
public List<INPUT> getInputsWithoutProvides() {
return Collections.<INPUT>unmodifiableList(noProvides);
}
private static <T> List<T> topologicalStableSort(
List<T> items, Multimap<T, T> deps) {
if (items
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> if (reference.isHoistedFunction()) {
blocksWithDeclarations.add(reference.getBasicBlock());
isDeclaredInScope = true;
hoistedFn = reference;
break;
} else if (NodeUtil.isFunctionDeclaration(
reference.getNode().getParent())) {
isUnhoistedNamedFunction = true;
}
}
for (Reference reference : references) {
if (reference == hoistedFn) {
continue;
}
BasicBlock basicBlock = reference.getBasicBlock();
boolean isDeclaration = reference.isDeclaration();
boolean allowDupe =
SyntacticScopeCreator.hasDuplicateDeclarationSuppression(
reference.getNode(), v);
if (isDeclaration && !allowDupe) {
// Look through all the declarations we've found so far, and
// check if any of them are before this block.
for (BasicBlock declaredBlock : blocksWithDeclarations) {
if (declaredBlock.provablyExecutesBefore(basicBlock)) {
// TODO(johnlenz): Fix AST generating clients that so they would
// have property StaticSourceFile attached at each node. Or
// better yet, make sure the generated code never violates
// the requirement to pass aggressive var check!
String filename = NodeUtil.getSourceName(reference.getNode());
compiler.report(
JSError.make(filename,
reference.getNode(),
checkLevel,
REDECLARED_VARIABLE, v.name));
break;
}
}
}
if (isUnhoistedNamedFunction && !isDeclaration && isDeclaredInScope) {
// Only allow an unhoisted named function to be used within the
// block it is declared.
for (BasicBlock declaredBlock : blocksWithDeclarations) {
if (!declaredBlock.provablyExecutesBefore(basicBlock)) {
String filename = NodeUtil.getSourceName(reference.getNode());
compiler.report(
JSError.make(filename,
reference.getNode(),
AMBIGUOUS_FUNCTION_DECL, v.name));
break;
}
}
}
if (!isDeclaration && !isDeclaredInScope) {
// Don't check the order of refer in externs files.
if (!reference.getNode().isFromExterns()) {
// Special case to deal with var goog = goog || {}
Node grandparent = reference.getGrandparent();
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>.valueOf(self.nextUniqueNameId());
}
};
}
@Override
boolean areNodesEqualForInlining(Node n1, Node n2) {
if (options.ambiguateProperties ||
options.disambiguateProperties) {
// The type based optimizations require that type information is preserved
// during other optimizations.
return n1.isEquivalentToTyped(n2);
} else {
return n1.isEquivalentTo(n2);
}
}
//------------------------------------------------------------------------
// Inputs
//------------------------------------------------------------------------
// TODO(nicksantos): Decide which parts of these belong in an AbstractCompiler
// interface, and which ones should always be injected.
@Override
public CompilerInput getInput(InputId id) {
return inputsById.get(id);
}
/**
* Removes an input file from AST.
* @param id The id of the input to be removed.
*/
protected void removeExternInput(InputId id) {
CompilerInput input = getInput(id);
if (input == null) {
return;
}
Preconditions.checkState(input.isExtern(), "Not an extern input: %s", input.getName());
inputsById.remove(id);
externs.remove(input);
Node root = input.getAstRoot(this);
if (root != null) {
root.detachFromParent();
}
}
@Override
public CompilerInput newExternInput(String name) {
SourceAst ast = new SyntheticAst(name);
if (inputsById.containsKey(ast.getInputId())) {
throw new IllegalArgumentException("Conflicting externs name: " + name);
}
CompilerInput input = new CompilerInput(ast, true);
putCompilerInput(input.getInputId(), input);
externsRoot.addChildToFront(ast.getAstRoot(this));
externs.add(0, input);
return input;
}
private CompilerInput putCompilerInput(InputId id, CompilerInput input) {
input.setCompiler(this);
return inputsById.put(id, input);
}
/** Add a source input dynamically. Intended for incremental compilation. */
void addIncrementalSourceAst(JsAst ast) {
InputId id = ast.getInputId();
Preconditions.checkState(
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>getInput(id) == null, "Duplicate input %s", id.getIdName());
putCompilerInput(id, new CompilerInput(ast));
}
/**
* Replace a source input dynamically. Intended for incremental
* re-compilation.
*
* If the new source input doesn't parse, then keep the old input
* in the AST and return false.
*
* @return Whether the new AST was attached successfully.
*/
boolean replaceIncrementalSourceAst(JsAst ast) {
CompilerInput oldInput = getInput(ast.getInputId());
Preconditions.checkNotNull(oldInput, "No input to replace: %s", ast.getInputId().getIdName());
Node newRoot = ast.getAstRoot(this);
if (newRoot == null) {
return false;
}
Node oldRoot = oldInput.getAstRoot(this);
if (oldRoot != null) {
oldRoot.getParent().replaceChild(oldRoot, newRoot);
} else {
getRoot().getLastChild().addChildToBack(newRoot);
}
CompilerInput newInput = new CompilerInput(ast);
putCompilerInput(ast.getInputId(), newInput);
JSModule module = oldInput.getModule();
if (module != null) {
module.addAfter(newInput, oldInput);
module.remove(oldInput);
}
// Verify the input id is set properly.
Preconditions.checkState(
newInput.getInputId().equals(oldInput.getInputId()));
InputId inputIdOnAst = newInput.getAstRoot(this).getInputId();
Preconditions.checkState(newInput.getInputId().equals(inputIdOnAst));
inputs.remove(oldInput);
return true;
}
/**
* Add a new source input dynamically. Intended for incremental compilation.
* <p>
* If the new source input doesn't parse, it will not be added, and a false
* will be returned.
*
* @param ast the JS Source to add.
* @return true if the source was added successfully, false otherwise.
* @throws IllegalStateException if an input for this ast already exists.
*/
boolean addNewSourceAst(JsAst ast) {
CompilerInput oldInput = getInput(ast.getInputId());
if (oldInput != null) {
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>
throw new IllegalStateException(
"Input already exists: " + ast.getInputId().getIdName());
}
Node newRoot = ast.getAstRoot(this);
if (newRoot == null) {
return false;
}
getRoot().getLastChild().addChildToBack(newRoot);
CompilerInput newInput = new CompilerInput(ast);
// TODO(tylerg): handle this for multiple modules at some point.
if (moduleGraph == null && !modules.isEmpty()) {
// singleton module
modules.get(0).add(newInput);
}
putCompilerInput(ast.getInputId(), newInput);
return true;
}
@Override
JSModuleGraph getModuleGraph() {
return moduleGraph;
}
/**
* Gets a module graph. This will always return a module graph, even
* in the degenerate case when there's only one module.
*/
JSModuleGraph getDegenerateModuleGraph() {
return moduleGraph == null ? new JSModuleGraph(modules) : moduleGraph;
}
@Override
public JSTypeRegistry getTypeRegistry() {
if (typeRegistry == null) {
typeRegistry = new JSTypeRegistry(oldErrorReporter, options.looseTypes);
}
return typeRegistry;
}
@Override
public MemoizedScopeCreator getTypedScopeCreator() {
return getPassConfig().getTypedScopeCreator();
}
@SuppressWarnings("unchecked")
DefaultPassConfig ensureDefaultPassConfig() {
PassConfig passes = getPassConfig().getBasePassConfig();
Preconditions.checkState(passes instanceof DefaultPassConfig,
"PassConfigs must eventually delegate to the DefaultPassConfig");
return (DefaultPassConfig) passes;
}
public SymbolTable buildKnownSymbolTable() {
SymbolTable symbolTable = new SymbolTable(getTypeRegistry());
MemoizedScopeCreator typedScopeCreator = getTypedScopeCreator();
if (typedScopeCreator != null) {
symbolTable.addScopes(typedScopeCreator.getAllMemoizedScopes());
symbolTable.addSymbolsFrom(typedScopeCreator);
} else {
symbolTable.findScopes(this, externsRoot, jsRoot);
}
GlobalNamespace globalNamespace =
ensureDefaultPassConfig().getGlobalNamespace();
if (globalNamespace != null) {
symbolTable.addSymbolsFrom(globalNamespace);
}
ReferenceCollecting
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>(Node n) {
if (phaseOptimizer != null) {
phaseOptimizer.reportChangeToEnclosingScope(n);
phaseOptimizer.startCrossScopeReporting();
reportCodeChange();
phaseOptimizer.endCrossScopeReporting();
} else {
reportCodeChange();
}
}
/**
* Some tests don't want to call the compiler "wholesale," they may not want
* to call check and/or optimize. With this method, tests can execute custom
* optimization loops.
*/
@VisibleForTesting
void setPhaseOptimizer(PhaseOptimizer po) {
this.phaseOptimizer = po;
}
@Override
public void reportCodeChange() {
for (CodeChangeHandler handler : codeChangeHandlers) {
handler.reportChange();
}
}
@Override
public CodingConvention getCodingConvention() {
CodingConvention convention = options.getCodingConvention();
convention = convention != null ? convention : defaultCodingConvention;
return convention;
}
@Override
public boolean isIdeMode() {
return options.ideMode;
}
@Override
public boolean acceptEcmaScript5() {
switch (options.getLanguageIn()) {
case ECMASCRIPT5:
case ECMASCRIPT5_STRICT:
return true;
case ECMASCRIPT3:
return false;
}
throw new IllegalStateException("unexpected language mode");
}
public LanguageMode languageMode() {
return options.getLanguageIn();
}
@Override
public boolean acceptConstKeyword() {
return options.acceptConstKeyword;
}
@Override
Config getParserConfig() {
if (parserConfig == null) {
Config.LanguageMode mode;
switch (options.getLanguageIn()) {
case ECMASCRIPT3:
mode = Config.LanguageMode.ECMASCRIPT3;
break;
case ECMASCRIPT5:
mode = Config.LanguageMode.ECMASCRIPT5;
break;
case ECMASCRIPT5_STRICT:
mode = Config.LanguageMode.ECMASCRIPT5_STRICT;
break;
default:
throw new IllegalStateException("unexpected language mode");
}
parserConfig = ParserRunner.createConfig(
isIdeMode(),
mode,
acceptConstKeyword(),
options.extraAnnotationNames);
}
return parserConfig;
}
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> runHotSwap(originalRoot, js, this.getCleanupPassConfig());
// NOTE: If hot swap passes that use GlobalNamespace are added, we will need
// to revisit this approach to clearing GlobalNamespaces
runHotSwapPass(null, null, ensureDefaultPassConfig().garbageCollectChecks);
this.getTypeRegistry().clearNamedTypes();
this.removeSyntheticVarsInput();
runHotSwap(originalRoot, js, this.ensureDefaultPassConfig());
}
/**
* Execute the passes from a PassConfig instance over a single replaced file.
*/
private void runHotSwap(
Node originalRoot, Node js, PassConfig passConfig) {
for (PassFactory passFactory : passConfig.getChecks()) {
runHotSwapPass(originalRoot, js, passFactory);
}
}
private void runHotSwapPass(
Node originalRoot, Node js, PassFactory passFactory) {
HotSwapCompilerPass pass = passFactory.getHotSwapPass(this);
if (pass != null) {
logger.info("Performing HotSwap for pass " + passFactory.getName());
pass.hotSwapScript(js, originalRoot);
}
}
private PassConfig getCleanupPassConfig() {
return new CleanupPasses(getOptions());
}
private void removeSyntheticVarsInput() {
String sourceName = Compiler.SYNTHETIC_EXTERNS;
removeExternInput(new InputId(sourceName));
}
@Override
Node ensureLibraryInjected(String resourceName) {
if (injectedLibraries.containsKey(resourceName)) {
return null;
}
// All libraries depend on js/base.js
boolean isBase = "base".equals(resourceName);
if (!isBase) {
ensureLibraryInjected("base");
}
Node firstChild = loadLibraryCode(resourceName).removeChildren();
Node lastChild = firstChild.getLastSibling();
Node parent = getNodeForCodeInsertion(null);
if (isBase) {
parent.addChildrenToFront(firstChild);
} else {
parent.addChildrenAfter(
firstChild, injectedLibraries.get("base"));
}
reportCodeChange();
injectedLibraries.put(resourceName, lastChild);
return lastChild;
}
/** Load a library as a resource */
@VisibleForTesting
Node loadLibraryCode
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> Token.NULL:
case Token.THIS:
case Token.TRUE:
validateChildless(n);
return;
// General unary ops
case Token.DELPROP:
case Token.POS:
case Token.NEG:
case Token.NOT:
case Token.INC:
case Token.DEC:
case Token.TYPEOF:
case Token.VOID:
case Token.BITNOT:
case Token.CAST:
validateUnaryOp(n);
return;
// General binary ops
case Token.COMMA:
case Token.OR:
case Token.AND:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
case Token.EQ:
case Token.NE:
case Token.SHEQ:
case Token.SHNE:
case Token.LT:
case Token.GT:
case Token.LE:
case Token.GE:
case Token.INSTANCEOF:
case Token.IN:
case Token.LSH:
case Token.RSH:
case Token.URSH:
case Token.SUB:
case Token.ADD:
case Token.MUL:
case Token.MOD:
case Token.DIV:
validateBinaryOp(n);
return;
// Assignments
case Token.ASSIGN:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.ASSIGN_URSH:
case Token.ASSIGN_ADD:
case Token.ASSIGN_SUB:
case Token.ASSIGN_MUL:
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
validateAssignmentExpression(n);
return;
case Token.HOOK:
validateTrinaryOp(n);
return;
// Node types that require special handling
case Token.STRING:
validateString(n);
return;
case Token.NUMBER:
validateNumber(n);
return;
case Token.NAME:
validateName(n);
return;
case Token.GETELEM:
validateBinaryOp(n);
return;
case Token.GETPROP:
validateGetProp(n);
return;
case Token.ARRAYLIT:
validateArrayLit(n);
return;
case
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>Compiler compiler, RedeclarationHandler redeclarationHandler) {
this.compiler = compiler;
this.redeclarationHandler = redeclarationHandler;
}
@Override
public Scope createScope(Node n, Scope parent) {
inputId = null;
if (parent == null) {
scope = Scope.createGlobalScope(n);
} else {
scope = new Scope(parent, n);
}
scanRoot(n);
inputId = null;
Scope returnedScope = scope;
scope = null;
return returnedScope;
}
private void scanRoot(Node n) {
if (n.isFunction()) {
if (inputId == null) {
inputId = NodeUtil.getInputId(n);
// TODO(johnlenz): inputId maybe null if the FUNCTION node is detached
// from the AST.
// Is it meaningful to build a scope for detached FUNCTION node?
}
final Node fnNameNode = n.getFirstChild();
final Node args = fnNameNode.getNext();
final Node body = args.getNext();
// Bleed the function name into the scope, if it hasn't
// been declared in the outer scope.
String fnName = fnNameNode.getString();
if (!fnName.isEmpty() && NodeUtil.isFunctionExpression(n)) {
declareVar(fnNameNode);
}
// Args: Declare function variables
Preconditions.checkState(args.isParamList());
for (Node a = args.getFirstChild(); a != null;
a = a.getNext()) {
Preconditions.checkState(a.isName());
declareVar(a);
}
// Body
scanVars(body);
} else {
// It's the global block
Preconditions.checkState(scope.getParent() == null);
scanVars(n);
}
}
/**
* Scans and gather variables declarations under a Node
*/
private void scanVars(Node n) {
switch (n.getType()) {
case Token.VAR:
// Declare all variables. e.g. var x = 1, y, z;
for (Node child = n.getFirstChild();
child != null;) {
Node next = child.getNext();
declareVar(child);
child = next;
}
return;
case Token.FUNCTION:
if (
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>NodeUtil.isFunctionExpression(n)) {
return;
}
String fnName = n.getFirstChild().getString();
if (fnName.isEmpty()) {
// This is invalid, but allow it so the checks can catch it.
return;
}
declareVar(n.getFirstChild());
return; // should not examine function's children
case Token.CATCH:
Preconditions.checkState(n.getChildCount() == 2);
Preconditions.checkState(n.getFirstChild().isName());
// the first child is the catch var and the third child
// is the code block
final Node var = n.getFirstChild();
final Node block = var.getNext();
declareVar(var);
scanVars(block);
return; // only one child to scan
case Token.SCRIPT:
inputId = n.getInputId();
Preconditions.checkNotNull(inputId);
break;
}
// Variables can only occur in statement-level nodes, so
// we only need to traverse children in a couple special cases.
if (NodeUtil.isControlStructure(n) || NodeUtil.isStatementBlock(n)) {
for (Node child = n.getFirstChild();
child != null;) {
Node next = child.getNext();
scanVars(child);
child = next;
}
}
}
/**
* Interface for injectable duplicate handling.
*/
interface RedeclarationHandler {
void onRedeclaration(
Scope s, String name, Node n, CompilerInput input);
}
/**
* The default handler for duplicate declarations.
*/
private class DefaultRedeclarationHandler implements RedeclarationHandler {
@Override
public void onRedeclaration(
Scope s, String name, Node n, CompilerInput input) {
Node parent = n.getParent();
// Don't allow multiple variables to be declared at the top-level scope
if (scope.isGlobal()) {
Scope.Var origVar = scope.getVar(name);
Node origParent = origVar.getParentNode();
if (origParent.isCatch() &&
parent.isCatch()) {
// Okay, both are 'catch(x)' variables.
return;
}
boolean allowDupe = hasDuplicateDeclarationSuppression(n, origVar);
if (!allowDupe) {
compiler.report(
JSError.make
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>(NodeUtil.getSourceName(n), n,
VAR_MULTIPLY_DECLARED_ERROR,
name,
(origVar.input != null
? origVar.input.getName()
: "??")));
}
} else if (name.equals(ARGUMENTS) && !NodeUtil.isVarDeclaration(n)) {
// Disallow shadowing "arguments" as we can't handle with our current
// scope modeling.
compiler.report(
JSError.make(NodeUtil.getSourceName(n), n,
VAR_ARGUMENTS_SHADOWED_ERROR));
}
}
}
/**
* Declares a variable.
*
* @param n The node corresponding to the variable name.
*/
private void declareVar(Node n) {
Preconditions.checkState(n.isName());
CompilerInput input = compiler.getInput(inputId);
String name = n.getString();
if (scope.isDeclared(name, false)
|| (scope.isLocal() && name.equals(ARGUMENTS))) {
redeclarationHandler.onRedeclaration(
scope, name, n, input);
} else {
scope.declare(name, n, null, input);
}
}
/**
* @param n The name node to check.
* @param origVar The associated Var.
* @return Whether duplicated declarations warnings should be suppressed
* for the given node.
*/
static boolean hasDuplicateDeclarationSuppression(Node n, Scope.Var origVar) {
Preconditions.checkState(n.isName());
Node parent = n.getParent();
Node origParent = origVar.getParentNode();
JSDocInfo info = n.getJSDocInfo();
if (info == null) {
info = parent.getJSDocInfo();
}
if (info != null && info.getSuppressions().contains("duplicate")) {
return true;
}
info = origVar.nameNode.getJSDocInfo();
if (info == null) {
info = origParent.getJSDocInfo();
}
return (info != null && info.getSuppressions().contains("duplicate"));
}
/**
* Generates an untyped global scope from the root of AST of compiler (which
* includes externs).
*
*
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>/*
* Copyright 2011 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.rhino.Node;
/**
* {@link CheckDebuggerStatement} checks for the presence of the "debugger"
* statement in JavaScript code. It is appropriate to use this statement while
* developing JavaScript; however, it is generally undesirable to include it in
* production code.
*
* @author bolinfest@google.com (Michael Bolin)
*/
class CheckDebuggerStatement extends AbstractPostOrderCallback
implements CompilerPass {
static final DiagnosticType DEBUGGER_STATEMENT_PRESENT =
DiagnosticType.disabled("JSC_DEBUGGER_STATEMENT_PRESENT",
"Using the debugger statement can halt your application if the user " +
"has a JavaScript debugger running.");
private final AbstractCompiler compiler;
public CheckDebuggerStatement(AbstractCompiler compiler) {
this.compiler = compiler;
}
@Override
public void process(Node externs, Node root) {
NodeTraversal.traverse(compiler, root, this);
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isDebugger()) {
t.report(n, DEBUGGER_STATEMENT_PRESENT);
}
}
}
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> final DiagnosticType ARGUMENTS_DECLARATION = DiagnosticType.warning(
"JSC_ARGUMENTS_DECLARATION",
"\"arguments\" cannot be redeclared in ES5 strict mode");
static final DiagnosticType ARGUMENTS_ASSIGNMENT = DiagnosticType.warning(
"JSC_ARGUMENTS_ASSIGNMENT",
"the \"arguments\" object cannot be reassigned in ES5 strict mode");
static final DiagnosticType DELETE_VARIABLE = DiagnosticType.warning(
"JSC_DELETE_VARIABLE",
"variables, functions, and arguments cannot be deleted in "
+ "ES5 strict mode");
static final DiagnosticType ILLEGAL_NAME = DiagnosticType.error(
"JSC_ILLEGAL_NAME",
"identifiers ending in '__' cannot be used in Caja");
static final DiagnosticType DUPLICATE_OBJECT_KEY = DiagnosticType.warning(
"JSC_DUPLICATE_OBJECT_KEY",
"object literals cannot contain duplicate keys in ES5 strict mode");
static final DiagnosticType BAD_FUNCTION_DECLARATION = DiagnosticType.error(
"JSC_BAD_FUNCTION_DECLARATION",
"functions can only be declared at top level or immediately within " +
"another function in ES5 strict mode");
private final AbstractCompiler compiler;
private final boolean noVarCheck;
private final boolean noCajaChecks;
StrictModeCheck(AbstractCompiler compiler) {
this(compiler, false, false);
}
StrictModeCheck(
AbstractCompiler compiler, boolean noVarCheck, boolean noCajaChecks) {
this.compiler = compiler;
this.noVarCheck = noVarCheck;
this.noCajaChecks = noCajaChecks;
}
@Override public void process(Node externs, Node root) {
NodeTraversal.traverseRoots(
compiler, Lists.newArrayList(externs, root), this);
NodeTraversal.traverse(compiler, root, new NonExternChecks());
}
@Override public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isFunction()) {
checkFunctionUse(t, n);
} else if (n.isName()) {
if (!isDeclaration(n)) {
checkNameUse(t, n);
}
} else if (n.isAssign()) {
checkAssignment(t
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>, n);
} else if (n.isDelProp()) {
checkDelete(t, n);
} else if (n.isObjectLit()) {
checkObjectLiteral(t, n);
} else if (n.isLabel()) {
checkLabel(t, n);
}
}
/** Checks that the function is used legally. */
private void checkFunctionUse(NodeTraversal t, Node n) {
if (NodeUtil.isFunctionDeclaration(n) && !NodeUtil.isHoistedFunctionDeclaration(n)) {
t.report(n, BAD_FUNCTION_DECLARATION);
}
}
/**
* Determines if the given name is a declaration, which can be a declaration
* of a variable, function, or argument.
*/
private static boolean isDeclaration(Node n) {
switch (n.getParent().getType()) {
case Token.VAR:
case Token.FUNCTION:
case Token.CATCH:
return true;
case Token.PARAM_LIST:
return n.getParent().getParent().isFunction();
default:
return false;
}
}
/** Checks that the given name is used legally. */
private void checkNameUse(NodeTraversal t, Node n) {
Var v = t.getScope().getVar(n.getString());
if (v == null) {
// In particular, this prevents creating a global variable by assigning
// to it without a declaration.
if (!noVarCheck) {
t.report(n, UNKNOWN_VARIABLE, n.getString());
}
}
if (!noCajaChecks) {
if ("eval".equals(n.getString())) {
t.report(n, EVAL_USE);
} else if (n.getString().endsWith("__")) {
t.report(n, ILLEGAL_NAME);
}
}
}
/** Checks that an assignment is not to the "arguments" object. */
private void checkAssignment(NodeTraversal t, Node n) {
if (n.getFirstChild().isName()) {
if ("arguments".equals(n.getFirstChild().getString())) {
t.report(n, ARGUMENTS_ASSIGNMENT);
} else if ("eval".equals(n.getFirstChild().getString())) {
// Note that assignment to eval is already illegal because any use of
// that
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> name is illegal.
if (noCajaChecks) {
t.report(n, EVAL_ASSIGNMENT);
}
}
}
}
/** Checks that variables, functions, and arguments are not deleted. */
private void checkDelete(NodeTraversal t, Node n) {
if (n.getFirstChild().isName()) {
Var v = t.getScope().getVar(n.getFirstChild().getString());
if (v != null) {
t.report(n, DELETE_VARIABLE);
}
}
}
/** Checks that object literal keys are valid. */
private void checkObjectLiteral(NodeTraversal t, Node n) {
Set<String> getters = Sets.newHashSet();
Set<String> setters = Sets.newHashSet();
for (Node key = n.getFirstChild();
key != null;
key = key.getNext()) {
if (!noCajaChecks && key.getString().endsWith("__")) {
t.report(key, ILLEGAL_NAME);
}
if (!key.isSetterDef()) {
// normal property and getter cases
if (getters.contains(key.getString())) {
t.report(key, DUPLICATE_OBJECT_KEY);
} else {
getters.add(key.getString());
}
}
if (!key.isGetterDef()) {
// normal property and setter cases
if (setters.contains(key.getString())) {
t.report(key, DUPLICATE_OBJECT_KEY);
} else {
setters.add(key.getString());
}
}
}
}
/** Checks that label names are valid. */
private void checkLabel(NodeTraversal t, Node n) {
if (n.getFirstChild().getString().endsWith("__")) {
if (!noCajaChecks) {
t.report(n.getFirstChild(), ILLEGAL_NAME);
}
}
}
/** Checks that are performed on non-extern code only. */
private class NonExternChecks extends AbstractPostOrderCallback {
@Override public void visit(NodeTraversal t, Node n, Node parent) {
if ((n.isName()) && isDeclaration(n)) {
checkDeclaration(t, n);
} else if (n.isGetProp()) {
checkProperty(t
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>can.c.
*/
public final static int
ERROR = -1,
RETURN = 4,
BITOR = 9,
BITXOR = 10,
BITAND = 11,
EQ = 12,
NE = 13,
LT = 14,
LE = 15,
GT = 16,
GE = 17,
LSH = 18,
RSH = 19,
URSH = 20,
ADD = 21,
SUB = 22,
MUL = 23,
DIV = 24,
MOD = 25,
NOT = 26,
BITNOT = 27,
POS = 28,
NEG = 29,
NEW = 30,
DELPROP = 31,
TYPEOF = 32,
GETPROP = 33,
GETELEM = 35,
CALL = 37,
NAME = 38,
NUMBER = 39,
STRING = 40,
NULL = 41,
THIS = 42,
FALSE = 43,
TRUE = 44,
SHEQ = 45, // shallow equality (===)
SHNE = 46, // shallow inequality (!==)
REGEXP = 47,
THROW = 49,
IN = 51,
INSTANCEOF = 52,
ARRAYLIT = 63, // array literal
OBJECTLIT = 64, // object literal
TRY = 77,
PARAM_LIST = 83,
COMMA = 85, // comma operator
ASSIGN = 86, // simple assignment (=)
ASSIGN_BITOR = 87, // |=
ASSIGN_BITXOR = 88, // ^=
ASSIGN_BITAND = 89, // &=
ASSIGN_LSH = 90, // <<=
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>
ASSIGN_RSH = 91, // >>=
ASSIGN_URSH = 92, // >>>=
ASSIGN_ADD = 93, // +=
ASSIGN_SUB = 94, // -=
ASSIGN_MUL = 95, // *=
ASSIGN_DIV = 96, // /=
ASSIGN_MOD = 97, // %=
HOOK = 98, // conditional (?:)
OR = 100, // logical or (||)
AND = 101, // logical and (&&)
INC = 102, // increment (++)
DEC = 103, // decrement (--)
FUNCTION = 105, // function keyword
IF = 108, // if keyword
SWITCH = 110, // switch keyword
CASE = 111, // case keyword
DEFAULT_CASE = 112, // default keyword
WHILE = 113, // while keyword
DO = 114, // do keyword
FOR = 115, // for keyword
BREAK = 116, // break keyword
CONTINUE = 117, // continue keyword
VAR = 118, // var keyword
WITH = 119, // with keyword
CATCH = 120, // catch keyword
VOID = 122, // void keyword
EMPTY = 124,
BLOCK = 125, // statement block
LABEL = 126, // label
EXPR_RESULT = 130, // expression statement in scripts
SCRIPT = 132, // top-level node for entire script
GETTER_DEF = 147,
SETTER_DEF = 148,
CONST = 149, // JS 1.5 const keyword
DEBUGGER = 152,
// JSCompiler introduced tokens
LABEL_NAME = 153,
STRING_KEY = 154, // object literal key
CAST = 155,
// J
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>SDoc-only tokens
ANNOTATION = 300,
PIPE = 301,
STAR = 302,
EOC = 303,
QMARK = 304,
ELLIPSIS = 305,
BANG = 306,
EQUALS = 307,
LB = 308, // left brackets
LC = 309, // left curly braces
COLON = 310;
// Transitional definitions
// TODO(johnlenz): remove these
public final static int
DEFAULT = DEFAULT_CASE,
GET = GETTER_DEF,
LP = PARAM_LIST,
SET = SETTER_DEF;
public static String name(int token) {
switch (token) {
case ERROR: return "ERROR";
case RETURN: return "RETURN";
case BITOR: return "BITOR";
case BITXOR: return "BITXOR";
case BITAND: return "BITAND";
case EQ: return "EQ";
case NE: return "NE";
case LT: return "LT";
case LE: return "LE";
case GT: return "GT";
case GE: return "GE";
case LSH: return "LSH";
case RSH: return "RSH";
case URSH: return "URSH";
case ADD: return "ADD";
case SUB: return "SUB";
case MUL: return "MUL";
case DIV: return "DIV";
case MOD: return "MOD";
case NOT: return "NOT";
case BITNOT: return "BITNOT";
case POS: return "POS";
case NEG: return "NEG";
case NEW: return "NEW";
case DELPROP: return "DELPROP";
case TYPEOF: return "TYPEOF";
case GETPROP: return "GETPROP";
case GETELEM: return "GETELEM";
case CALL: return "CALL";
case NAME: return "NAME";
case LABEL_NAME: return "LABEL_NAME";
case NUMBER: return
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> "NUMBER";
case STRING: return "STRING";
case STRING_KEY: return "STRING_KEY";
case NULL: return "NULL";
case THIS: return "THIS";
case FALSE: return "FALSE";
case TRUE: return "TRUE";
case SHEQ: return "SHEQ";
case SHNE: return "SHNE";
case REGEXP: return "REGEXP";
case THROW: return "THROW";
case IN: return "IN";
case INSTANCEOF: return "INSTANCEOF";
case ARRAYLIT: return "ARRAYLIT";
case OBJECTLIT: return "OBJECTLIT";
case TRY: return "TRY";
case PARAM_LIST: return "PARAM_LIST";
case COMMA: return "COMMA";
case ASSIGN: return "ASSIGN";
case ASSIGN_BITOR: return "ASSIGN_BITOR";
case ASSIGN_BITXOR: return "ASSIGN_BITXOR";
case ASSIGN_BITAND: return "ASSIGN_BITAND";
case ASSIGN_LSH: return "ASSIGN_LSH";
case ASSIGN_RSH: return "ASSIGN_RSH";
case ASSIGN_URSH: return "ASSIGN_URSH";
case ASSIGN_ADD: return "ASSIGN_ADD";
case ASSIGN_SUB: return "ASSIGN_SUB";
case ASSIGN_MUL: return "ASSIGN_MUL";
case ASSIGN_DIV: return "ASSIGN_DIV";
case ASSIGN_MOD: return "ASSIGN_MOD";
case HOOK: return "HOOK";
case OR: return "OR";
case AND: return "AND";
case INC: return "INC";
case DEC: return "DEC";
case FUNCTION: return "FUNCTION";
case IF: return "IF";
case SWITCH: return "SWITCH";
case CASE: return "CASE";
case DEFAULT_CASE: return "DEFAULT_CASE";
case WHILE: return "WHILE";
case DO: return "DO";
case FOR: return "FOR";
case BREAK: return "BREAK";
case CONTINUE: return "CONTINUE";
case VAR: return "VAR";
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>)
? CheckLevel.ERROR : CheckLevel.WARNING;
// TODO(anatol): add flag that decides whether to process UNNAMED messages.
// Some projects would not want such functionality (unnamed) as they don't
// use SOY templates.
}
@Override
public void process(Node externs, Node root) {
NodeTraversal.traverse(compiler, root, this);
for (Map.Entry<Node, String> msgNode : googMsgNodes.entrySet()) {
compiler.report(JSError.make(msgNode.getValue(), msgNode.getKey(),
checkLevel, MESSAGE_NODE_IS_ORPHANED));
}
}
@Override
public void visit(NodeTraversal traversal, Node node, Node parent) {
String messageKey;
boolean isVar;
Node msgNode, msgNodeParent;
switch (node.getType()) {
case Token.NAME:
// var MSG_HELLO = 'Message'
if ((parent != null) && (parent.isVar())) {
messageKey = node.getString();
isVar = true;
} else {
return;
}
msgNode = node.getFirstChild();
msgNodeParent = node;
break;
case Token.ASSIGN:
// somenamespace.someclass.MSG_HELLO = 'Message'
isVar = false;
Node getProp = node.getFirstChild();
if (!getProp.isGetProp()) {
return;
}
Node propNode = getProp.getLastChild();
messageKey = propNode.getString();
msgNode = node.getLastChild();
msgNodeParent = node;
break;
case Token.CALL:
// goog.getMsg()
String fnName = node.getFirstChild().getQualifiedName();
if (MSG_FUNCTION_NAME.equals(fnName)) {
googMsgNodes.put(node, traversal.getSourceName());
} else if (MSG_FALLBACK_FUNCTION_NAME.equals(fnName)) {
visitFallbackFunctionCall(traversal, node);
}
return;
default:
return;
}
// Is this a message name?
boolean isNewStyleMessage =
msgNode != null && msgNode.isCall();
if (!isMessageName(messageKey, isNewStyleMessage)) {
return;
}
if (
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>msgNode == null) {
compiler.report(
traversal.makeError(node, MESSAGE_HAS_NO_VALUE, messageKey));
return;
}
// Just report a warning if a qualified messageKey that looks like a message
// (e.g. "a.b.MSG_X") doesn't use goog.getMsg().
if (isNewStyleMessage) {
googMsgNodes.remove(msgNode);
} else if (style != JsMessage.Style.LEGACY) {
compiler.report(traversal.makeError(node, checkLevel,
MESSAGE_NOT_INITIALIZED_USING_NEW_SYNTAX));
}
boolean isUnnamedMsg = isUnnamedMessageName(messageKey);
Builder builder = new Builder(
isUnnamedMsg ? null : messageKey);
builder.setSourceName(traversal.getSourceName());
try {
if (isVar) {
extractMessageFromVariable(builder, node, parent, parent.getParent());
} else {
extractMessageFromProperty(builder, node.getFirstChild(), node);
}
} catch (MalformedException ex) {
compiler.report(traversal.makeError(ex.getNode(),
MESSAGE_TREE_MALFORMED, ex.getMessage()));
return;
}
JsMessage extractedMessage = builder.build(idGenerator);
// If asked to check named internal messages.
if (needToCheckDuplications
&& !isUnnamedMsg
&& !extractedMessage.isExternal()) {
checkIfMessageDuplicated(messageKey, msgNode);
}
trackMessage(traversal, extractedMessage,
messageKey, msgNode, isUnnamedMsg);
if (extractedMessage.isEmpty()) {
// value of the message is an empty string. Translators do not like it.
compiler.report(traversal.makeError(node, MESSAGE_HAS_NO_TEXT,
messageKey));
}
// New-style messages must have descriptions. We don't emit a warning
// for legacy-style messages, because there are thousands of
// them in legacy code that are not worth the effort to fix, since they've
// already been translated anyway.
String desc = extractedMessage.getDesc();
if (isNewStyleMessage
&& (desc == null || desc.trim().isEmpty())
&& !extractedMessage.isExternal()) {
compiler.report(
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>);
if (objType != null && objType.getImplicitPrototype() != null) {
typeSystem.addInvalidatingType(objType.getImplicitPrototype());
recordInvalidationError(objType.getImplicitPrototype(), error);
}
}
}
/** Returns the property for the given name, creating it if necessary. */
protected Property getProperty(String name) {
if (!properties.containsKey(name)) {
properties.put(name, new Property(name));
}
return properties.get(name);
}
/** Public for testing. */
T getTypeWithProperty(String field, T type) {
return typeSystem.getTypeWithProperty(field, type);
}
/** Tracks the current type system scope while traversing. */
private abstract class AbstractScopingCallback implements ScopedCallback {
protected final Stack<StaticScope<T>> scopes =
new Stack<StaticScope<T>>();
@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
return true;
}
@Override
public void enterScope(NodeTraversal t) {
if (t.inGlobalScope()) {
scopes.push(typeSystem.getRootScope());
} else {
scopes.push(typeSystem.getFunctionScope(t.getScopeRoot()));
}
}
@Override
public void exitScope(NodeTraversal t) {
scopes.pop();
}
/** Returns the current scope at this point in the file. */
protected StaticScope<T> getScope() {
return scopes.peek();
}
}
/**
* Finds all properties defined in the externs file and sets them as
* ineligible for renaming from the type on which they are defined.
*/
private class FindExternProperties extends AbstractScopingCallback {
@Override public void visit(NodeTraversal t, Node n, Node parent) {
// TODO(johnlenz): Support object-literal property definitions.
if (n.isGetProp()) {
String field = n.getLastChild().getString();
T type = typeSystem.getType(getScope(), n.getFirstChild(), field);
Property prop = getProperty(field);
if (typeSystem.isInvalidatingType(type)) {
prop.invalidate();
} else {
prop.addTypeToSkip(type);
//
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> If this is a prototype property, then we want to skip assignments
// to the instance type as well. These assignments are not usually
// seen in the extern code itself, so we must handle them here.
if ((type = typeSystem.getInstanceFromPrototype(type)) != null) {
prop.getTypes().add(type);
prop.typesToSkip.add(type);
}
}
}
}
}
/**
* Traverses the tree, building a map from field names to Nodes for all
* fields that can be renamed.
*/
private class FindRenameableProperties extends AbstractScopingCallback {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isGetProp()) {
handleGetProp(t, n);
} else if (n.isObjectLit()) {
handleObjectLit(t, n);
}
}
/**
* Processes a GETPROP node.
*/
private void handleGetProp(NodeTraversal t, Node n) {
String name = n.getLastChild().getString();
T type = typeSystem.getType(getScope(), n.getFirstChild(), name);
Property prop = getProperty(name);
if (!prop.scheduleRenaming(n.getLastChild(),
processProperty(t, prop, type, null))) {
if (propertiesToErrorFor.containsKey(name)) {
String suggestion = "";
if (type instanceof JSType) {
JSType jsType = (JSType) type;
if (jsType.isAllType() || jsType.isUnknownType()) {
if (n.getFirstChild().isThis()) {
suggestion = "The \"this\" object is unknown in the function," +
"consider using @this";
} else {
String qName = n.getFirstChild().getQualifiedName();
suggestion = "Consider casting " + qName +
" if you know it's type.";
}
} else {
List<String> errors = Lists.newArrayList();
printErrorLocations(errors, jsType);
if (!errors.isEmpty()) {
suggestion = "Consider fixing errors for the following types:\n";
suggestion += Joiner.on("\n").join(errors);
}
}
}
compiler.report(JSError.make(
t.getSourceName(), n,
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> members of the union.
*/
private T processProperty(
NodeTraversal t, Property prop, T type, T relatedType) {
type = typeSystem.restrictByNotNullOrUndefined(type);
if (prop.skipRenaming || typeSystem.isInvalidatingType(type)) {
return null;
}
Iterable<T> alternatives = typeSystem.getTypeAlternatives(type);
if (alternatives != null) {
T firstType = relatedType;
for (T subType : alternatives) {
T lastType = processProperty(t, prop, subType, firstType);
if (lastType != null) {
firstType = firstType == null ? lastType : firstType;
}
}
return firstType;
} else {
T topType = typeSystem.getTypeWithProperty(prop.name, type);
if (typeSystem.isInvalidatingType(topType)) {
return null;
}
prop.addType(type, topType, relatedType);
return topType;
}
}
}
/** Renames all properties with references on more than one type. */
void renameProperties() {
int propsRenamed = 0, propsSkipped = 0, instancesRenamed = 0,
instancesSkipped = 0, singleTypeProps = 0;
Set<String> reported = Sets.newHashSet();
for (Property prop : properties.values()) {
if (prop.shouldRename()) {
Map<T, String> propNames = buildPropNames(prop.getTypes(), prop.name);
++propsRenamed;
prop.expandTypesToSkip();
for (Node node : prop.renameNodes) {
T rootType = prop.rootTypes.get(node);
if (prop.shouldRename(rootType)) {
String newName = propNames.get(rootType);
node.setString(newName);
compiler.reportCodeChange();
++instancesRenamed;
} else {
++instancesSkipped;
CheckLevel checkLevelForProp = propertiesToErrorFor.get(prop.name);
if (checkLevelForProp != null &&
checkLevelForProp != CheckLevel.OFF &&
!reported.contains(prop.name)) {
reported.add(prop.name);
compiler.report(JSError.make(
Node
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>/*
* Copyright 2009 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.Sets;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.jstype.FunctionType;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.JSTypeNative;
import com.google.javascript.rhino.jstype.JSTypeRegistry;
import com.google.javascript.rhino.jstype.ObjectType;
import java.util.Set;
/**
* A code generator that outputs type annotations for functions and
* constructors.
*/
class TypedCodeGenerator extends CodeGenerator {
private final JSTypeRegistry registry;
TypedCodeGenerator(
CodeConsumer consumer, CompilerOptions options, JSTypeRegistry registry) {
super(consumer, options);
Preconditions.checkNotNull(registry);
this.registry = registry;
}
@Override
void add(Node n, Context context) {
Node parent = n.getParent();
if (parent != null
&& (parent.isBlock()
|| parent.isScript())) {
if (n.isFunction()) {
add(getFunctionAnnotation(n));
} else if (n.isExprResult()
&& n.getFirstChild().isAssign()) {
Node rhs = n.getFirstChild().getLastChild();
add(getTypeAnnotation(rhs));
} else if (n.is
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> {2} property.");
static final DiagnosticType CONST_PROPERTY_REASSIGNED_VALUE =
DiagnosticType.warning(
"JSC_CONSTANT_PROPERTY_REASSIGNED_VALUE",
"constant property {0} assigned a value more than once");
static final DiagnosticType CONST_PROPERTY_DELETED =
DiagnosticType.warning(
"JSC_CONSTANT_PROPERTY_DELETED",
"constant property {0} cannot be deleted");
private final AbstractCompiler compiler;
private final TypeValidator validator;
// State about the current traversal.
private int deprecatedDepth = 0;
private int methodDepth = 0;
private JSType currentClass = null;
private final Multimap<String, String> initializedConstantProperties;
CheckAccessControls(AbstractCompiler compiler) {
this.compiler = compiler;
this.validator = compiler.getTypeValidator();
this.initializedConstantProperties = HashMultimap.create();
}
@Override
public void process(Node externs, Node root) {
NodeTraversal.traverse(compiler, root, this);
}
@Override
public void hotSwapScript(Node scriptRoot, Node originalRoot) {
NodeTraversal.traverse(compiler, scriptRoot, this);
}
@Override
public void enterScope(NodeTraversal t) {
if (!t.inGlobalScope()) {
Node n = t.getScopeRoot();
Node parent = n.getParent();
if (isDeprecatedFunction(n)) {
deprecatedDepth++;
}
if (methodDepth == 0) {
currentClass = getClassOfMethod(n, parent);
}
methodDepth++;
}
}
@Override
public void exitScope(NodeTraversal t) {
if (!t.inGlobalScope()) {
Node n = t.getScopeRoot();
if (isDeprecatedFunction(n)) {
deprecatedDepth--;
}
methodDepth--;
if (methodDepth == 0) {
currentClass = null;
}
}
}
/**
* Gets the type of the class that "owns" a method, or null if
* we know that its un-owned.
*/
private JSType getClassOfMethod(Node n, Node parent) {
if (parent.isAssign()) {
Node lValue = parent.getFirstChild();
if (NodeUtil.isGet(lValue)) {
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> // We have an assignment of the form "a.b = ...".
JSType lValueType = lValue.getJSType();
if (lValueType != null && lValueType.isNominalConstructor()) {
// If a.b is a constructor, then everything in this function
// belongs to the "a.b" type.
return (lValueType.toMaybeFunctionType()).getInstanceType();
} else {
// If a.b is not a constructor, then treat this as a method
// of whatever type is on "a".
return normalizeClassType(lValue.getFirstChild().getJSType());
}
} else {
// We have an assignment of the form "a = ...", so pull the
// type off the "a".
return normalizeClassType(lValue.getJSType());
}
} else if (NodeUtil.isFunctionDeclaration(n) ||
parent.isName()) {
return normalizeClassType(n.getJSType());
}
return null;
}
/**
* Normalize the type of a constructor, its instance, and its prototype
* all down to the same type (the instance type).
*/
private JSType normalizeClassType(JSType type) {
if (type == null || type.isUnknownType()) {
return type;
} else if (type.isNominalConstructor()) {
return (type.toMaybeFunctionType()).getInstanceType();
} else if (type.isFunctionPrototypeType()) {
FunctionType owner = ((ObjectType) type).getOwnerFunction();
if (owner.isConstructor()) {
return owner.getInstanceType();
}
}
return type;
}
@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.NAME:
checkNameDeprecation(t, n, parent);
checkNameVisibility(t, n, parent);
break;
case Token.GETPROP:
checkPropertyDeprecation(t, n, parent);
checkPropertyVisibility(t, n, parent);
checkConstantProperty(t, n);
break;
case Token.NEW:
checkConstructorDeprecation(t, n, parent);
break;
case Token.FUNCTION:
check
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>FinalClassOverrides(t, n, parent);
break;
}
}
/**
* Checks the given NEW node to ensure that access restrictions are obeyed.
*/
private void checkConstructorDeprecation(NodeTraversal t, Node n,
Node parent) {
JSType type = n.getJSType();
if (type != null) {
String deprecationInfo = getTypeDeprecationInfo(type);
if (deprecationInfo != null &&
shouldEmitDeprecationWarning(t, n, parent)) {
if (!deprecationInfo.isEmpty()) {
compiler.report(
t.makeError(n, DEPRECATED_CLASS_REASON,
type.toString(), deprecationInfo));
} else {
compiler.report(
t.makeError(n, DEPRECATED_CLASS, type.toString()));
}
}
}
}
/**
* Checks the given NAME node to ensure that access restrictions are obeyed.
*/
private void checkNameDeprecation(NodeTraversal t, Node n, Node parent) {
// Don't bother checking definitions or constructors.
if (parent.isFunction() || parent.isVar() ||
parent.isNew()) {
return;
}
Scope.Var var = t.getScope().getVar(n.getString());
JSDocInfo docInfo = var == null ? null : var.getJSDocInfo();
if (docInfo != null && docInfo.isDeprecated() &&
shouldEmitDeprecationWarning(t, n, parent)) {
if (docInfo.getDeprecationReason() != null) {
compiler.report(
t.makeError(n, DEPRECATED_NAME_REASON, n.getString(),
docInfo.getDeprecationReason()));
} else {
compiler.report(
t.makeError(n, DEPRECATED_NAME, n.getString()));
}
}
}
/**
* Checks the given GETPROP node to ensure that access restrictions are
* obeyed.
*/
private void checkPropertyDeprecation(NodeTraversal t, Node n, Node parent) {
// Don't bother checking constructors.
if (parent.isNew()) {
return;
}
ObjectType objectType =
ObjectType.cast(dereference(n.getFirstChild().getJSType()));
String propertyName = n.getLastChild().getString();
if (objectType != null
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>) {
String deprecationInfo
= getPropertyDeprecationInfo(objectType, propertyName);
if (deprecationInfo != null &&
shouldEmitDeprecationWarning(t, n, parent)) {
if (!deprecationInfo.isEmpty()) {
compiler.report(
t.makeError(n, DEPRECATED_PROP_REASON, propertyName,
validator.getReadableJSTypeName(n.getFirstChild(), true),
deprecationInfo));
} else {
compiler.report(
t.makeError(n, DEPRECATED_PROP, propertyName,
validator.getReadableJSTypeName(n.getFirstChild(), true)));
}
}
}
}
/**
* Determines whether the given name is visible in the current context.
* @param t The current traversal.
* @param name The name node.
*/
private void checkNameVisibility(NodeTraversal t, Node name, Node parent) {
Var var = t.getScope().getVar(name.getString());
if (var != null) {
JSDocInfo docInfo = var.getJSDocInfo();
if (docInfo != null) {
// If a name is private, make sure that we're in the same file.
Visibility visibility = docInfo.getVisibility();
if (visibility == Visibility.PRIVATE) {
StaticSourceFile varSrc = var.getSourceFile();
StaticSourceFile refSrc = name.getStaticSourceFile();
if (varSrc != null &&
refSrc != null &&
!varSrc.getName().equals(refSrc.getName())) {
if (docInfo.isConstructor() &&
isValidPrivateConstructorAccess(parent)) {
return;
}
compiler.report(
t.makeError(name, BAD_PRIVATE_GLOBAL_ACCESS,
name.getString(), varSrc.getName()));
}
}
}
}
}
/**
* Checks if a constructor is trying to override a final class.
*/
private void checkFinalClassOverrides(NodeTraversal t, Node fn, Node parent) {
JSType type = fn.getJSType().toMaybeFunctionType();
if (type != null && type.isConstructor()) {
JSType finalParentClass = getFinalParentClass(getClassOfMethod(fn, parent));
if (finalParentClass != null) {
compiler.report(
t.makeError(fn, EXTEND_FINAL_CLASS,
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>
type.getDisplayName(), finalParentClass.getDisplayName()));
}
}
}
/**
* Determines whether the given property with @const tag got reassigned
* @param t The current traversal.
* @param getprop The getprop node.
*/
private void checkConstantProperty(NodeTraversal t,
Node getprop) {
// Check whether the property is modified
Node parent = getprop.getParent();
boolean isDelete = parent.isDelProp();
if (!(NodeUtil.isAssignmentOp(parent) && parent.getFirstChild() == getprop)
&& !parent.isInc() && !parent.isDec()
&& !isDelete) {
return;
}
ObjectType objectType =
ObjectType.cast(dereference(getprop.getFirstChild().getJSType()));
String propertyName = getprop.getLastChild().getString();
boolean isConstant = isPropertyDeclaredConstant(objectType, propertyName);
// Check whether constant properties are reassigned
if (isConstant) {
if (isDelete) {
compiler.report(
t.makeError(getprop, CONST_PROPERTY_DELETED, propertyName));
return;
}
ObjectType oType = objectType;
while (oType != null) {
if (oType.hasReferenceName()) {
if (initializedConstantProperties.containsEntry(
oType.getReferenceName(), propertyName)) {
compiler.report(
t.makeError(getprop, CONST_PROPERTY_REASSIGNED_VALUE,
propertyName));
break;
}
}
oType = oType.getImplicitPrototype();
}
Preconditions.checkState(objectType.hasReferenceName());
initializedConstantProperties.put(objectType.getReferenceName(),
propertyName);
// Add the prototype when we're looking at an instance object
if (objectType.isInstanceType()) {
ObjectType prototype = objectType.getImplicitPrototype();
if (prototype != null) {
if (prototype.hasProperty(propertyName)
&& prototype.hasReferenceName()) {
initializedConstantProperties.put(prototype.getReferenceName(),
propertyName);
}
}
}
}
}
/**
* Determines whether the given property is visible in the current context.
* @param t The current traversal.
* @param getprop The getprop node.
*/
private void checkPropertyVisibility(NodeTraversal
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> t,
Node getprop, Node parent) {
ObjectType objectType =
ObjectType.cast(dereference(getprop.getFirstChild().getJSType()));
String propertyName = getprop.getLastChild().getString();
if (objectType != null) {
// Is this a normal property access, or are we trying to override
// an existing property?
boolean isOverride = parent.getJSDocInfo() != null &&
parent.isAssign() &&
parent.getFirstChild() == getprop;
// Find the lowest property defined on a class with visibility
// information.
if (isOverride) {
objectType = objectType.getImplicitPrototype();
}
JSDocInfo docInfo = null;
for (; objectType != null;
objectType = objectType.getImplicitPrototype()) {
docInfo = objectType.getOwnPropertyJSDocInfo(propertyName);
if (docInfo != null &&
docInfo.getVisibility() != Visibility.INHERITED) {
break;
}
}
if (objectType == null) {
// We couldn't find a visibility modifier; assume it's public.
return;
}
String referenceSource = getprop.getSourceFileName();
String definingSource = docInfo.getSourceName();
boolean sameInput = referenceSource != null
&& referenceSource.equals(definingSource);
Visibility visibility = docInfo.getVisibility();
JSType ownerType = normalizeClassType(objectType);
if (isOverride) {
// Check an ASSIGN statement that's trying to override a property
// on a superclass.
JSDocInfo overridingInfo = parent.getJSDocInfo();
Visibility overridingVisibility = overridingInfo == null ?
Visibility.INHERITED : overridingInfo.getVisibility();
// Check that (a) the property *can* be overridden, and
// (b) that the visibility of the override is the same as the
// visibility of the original property.
if (visibility == Visibility.PRIVATE && !sameInput) {
compiler.report(
t.makeError(getprop, PRIVATE_OVERRIDE,
objectType.toString()));
} else if (overridingVisibility != Visibility.INHERITED &&
overridingVisibility != visibility) {
compiler.report(
t.makeError(getprop, VISIBILITY_MISMATCH,
visibility.name(), objectType
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>.toString(),
overridingVisibility.name()));
}
} else {
if (sameInput) {
// private access is always allowed in the same file.
return;
} else if (visibility == Visibility.PRIVATE &&
(currentClass == null || !ownerType.isEquivalentTo(currentClass))) {
if (docInfo.isConstructor() &&
isValidPrivateConstructorAccess(parent)) {
return;
}
// private access is not allowed outside the file from a different
// enclosing class.
compiler.report(
t.makeError(getprop,
BAD_PRIVATE_PROPERTY_ACCESS,
propertyName,
validator.getReadableJSTypeName(
getprop.getFirstChild(), true)));
} else if (visibility == Visibility.PROTECTED) {
// There are 3 types of legal accesses of a protected property:
// 1) Accesses in the same file
// 2) Overriding the property in a subclass
// 3) Accessing the property from inside a subclass
// The first two have already been checked for.
if (currentClass == null || !currentClass.isSubtype(ownerType)) {
compiler.report(
t.makeError(getprop, BAD_PROTECTED_PROPERTY_ACCESS,
propertyName,
validator.getReadableJSTypeName(
getprop.getFirstChild(), true)));
}
}
}
}
}
/**
* Whether the given access of a private constructor is legal.
*
* For example,
* new PrivateCtor_(); // not legal
* PrivateCtor_.newInstance(); // legal
* x instanceof PrivateCtor_ // legal
*
* This is a weird special case, because our visibility system is inherited
* from Java, and JavaScript has no distinction between classes and
* constructors like Java does.
*
* We may want to revisit this if we decide to make the restrictions tighter.
*/
private static boolean isValidPrivateConstructorAccess(Node parent) {
return !parent.isNew();
}
/**
* Determines whether a deprecation warning should be emitted.
* @param t The current traversal.
* @param n The node which we are checking.
* @param parent The parent of the node which we are checking.
*/
private boolean shouldEmitDeprecationWarning(
NodeTraversal t, Node n, Node parent) {
// In
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> the global scope, there are only two kinds of accesses that should
// be flagged for warnings:
// 1) Calls of deprecated functions and methods.
// 2) Instantiations of deprecated classes.
// For now, we just let everything else by.
if (t.inGlobalScope()) {
if (!((parent.isCall() && parent.getFirstChild() == n) ||
n.isNew())) {
return false;
}
}
// We can always assign to a deprecated property, to keep it up to date.
if (n.isGetProp() && n == parent.getFirstChild() &&
NodeUtil.isAssignmentOp(parent)) {
return false;
}
return !canAccessDeprecatedTypes(t);
}
/**
* Returns whether it's currently OK to access deprecated names and
* properties.
*
* There are 3 exceptions when we're allowed to use a deprecated
* type or property:
* 1) When we're in a deprecated function.
* 2) When we're in a deprecated class.
* 3) When we're in a static method of a deprecated class.
*/
private boolean canAccessDeprecatedTypes(NodeTraversal t) {
Node scopeRoot = t.getScopeRoot();
Node scopeRootParent = scopeRoot.getParent();
return
// Case #1
(deprecatedDepth > 0) ||
// Case #2
(getTypeDeprecationInfo(t.getScope().getTypeOfThis()) != null) ||
// Case #3
(scopeRootParent != null && scopeRootParent.isAssign() &&
getTypeDeprecationInfo(
getClassOfMethod(scopeRoot, scopeRootParent)) != null);
}
/**
* Returns whether this is a function node annotated as deprecated.
*/
private static boolean isDeprecatedFunction(Node n) {
if (n.isFunction()) {
JSType type = n.getJSType();
if (type != null) {
return getTypeDeprecationInfo(type) != null;
}
}
return false;
}
/**
* Returns the deprecation reason for the type if it is marked
* as being deprecated. Returns empty string if the type is deprecated
* but no reason was given. Returns null if the type is not deprecated.
*/
private static String getTypeDeprecationInfo(JSType type
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>Blacklist;
Preconditions.checkState(blacklist != null && !blacklist.isEmpty(),
"Not checking use of goog.getCssName because of empty blacklist.");
return new CheckMissingGetCssName(
compiler, options.checkMissingGetCssNameLevel, blacklist);
}
};
/**
* Processes goog.getCssName. The cssRenamingMap is used to lookup
* replacement values for the classnames. If null, the raw class names are
* inlined.
*/
final PassFactory closureReplaceGetCssName =
new PassFactory("closureReplaceGetCssName", true) {
@Override
protected CompilerPass create(final AbstractCompiler compiler) {
return new CompilerPass() {
@Override
public void process(Node externs, Node jsRoot) {
Map<String, Integer> newCssNames = null;
if (options.gatherCssNames) {
newCssNames = Maps.newHashMap();
}
ReplaceCssNames pass = new ReplaceCssNames(
compiler,
newCssNames,
options.cssRenamingWhitelist);
pass.process(externs, jsRoot);
cssNames = newCssNames;
}
};
}
};
/**
* Creates synthetic blocks to prevent FoldConstants from moving code
* past markers in the source.
*/
final PassFactory createSyntheticBlocks =
new PassFactory("createSyntheticBlocks", true) {
@Override
protected CompilerPass create(AbstractCompiler compiler) {
return new CreateSyntheticBlocks(compiler,
options.syntheticBlockStartMarker,
options.syntheticBlockEndMarker);
}
};
/** Various peephole optimizations. */
final PassFactory peepholeOptimizations =
new PassFactory("peepholeOptimizations", false) {
@Override
protected CompilerPass create(AbstractCompiler compiler) {
final boolean late = false;
return new PeepholeOptimizationsPass(compiler,
new PeepholeMinimizeConditions(late),
new PeepholeSubstituteAlternateSyntax(late),
new PeepholeReplaceKnownMethods(late),
new PeepholeRemoveDeadCode(),
new PeepholeFoldConstants(late),
new PeepholeCollectPropertyAssignments());
}
};
/** Same as peepholeOptimizations but aggressively
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> merges code together */
final PassFactory latePeepholeOptimizations =
new PassFactory("latePeepholeOptimizations", true) {
@Override
protected CompilerPass create(AbstractCompiler compiler) {
final boolean late = true;
return new PeepholeOptimizationsPass(compiler,
new StatementFusion(options.aggressiveFusion),
new PeepholeRemoveDeadCode(),
new PeepholeMinimizeConditions(late),
new PeepholeSubstituteAlternateSyntax(late),
new PeepholeReplaceKnownMethods(late),
new PeepholeFoldConstants(late),
new ReorderConstantExpression());
}
};
/** Checks that all variables are defined. */
final HotSwapPassFactory checkVars =
new HotSwapPassFactory("checkVars", true) {
@Override
protected HotSwapCompilerPass create(AbstractCompiler compiler) {
return new VarCheck(compiler);
}
};
/** Checks for RegExp references. */
final PassFactory checkRegExp =
new PassFactory("checkRegExp", true) {
@Override
protected CompilerPass create(final AbstractCompiler compiler) {
final CheckRegExp pass = new CheckRegExp(compiler);
return new CompilerPass() {
@Override
public void process(Node externs, Node root) {
pass.process(externs, root);
compiler.setHasRegExpGlobalReferences(
pass.isGlobalRegExpPropertiesUsed());
}
};
}
};
/** Checks that references to variables look reasonable. */
final HotSwapPassFactory checkVariableReferences =
new HotSwapPassFactory("checkVariableReferences", true) {
@Override
protected HotSwapCompilerPass create(AbstractCompiler compiler) {
return new VariableReferenceCheck(
compiler, options.aggressiveVarCheck);
}
};
/** Pre-process goog.testing.ObjectPropertyString. */
final PassFactory objectPropertyStringPreprocess =
new PassFactory("ObjectPropertyStringPreprocess", true) {
@Override
protected CompilerPass create(AbstractCompiler compiler) {
return new ObjectPropertyStringPreprocess(compiler);
}
};
/** Creates a typed scope and adds types to the type registry. */
final HotSwapPassFactory resolveTypes =
new HotSwapPassFactory("resolveTypes", true) {
@Override
protected HotSwapCompilerPass create(AbstractCompiler compiler
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> scriptRoot, Node originalRoot) {
makeTypeCheck(compiler).check(scriptRoot, false);
}
};
}
};
/**
* Checks possible execution paths of the program for problems: missing return
* statements and dead code.
*/
final HotSwapPassFactory checkControlFlow =
new HotSwapPassFactory("checkControlFlow", true) {
@Override
protected HotSwapCompilerPass create(AbstractCompiler compiler) {
List<Callback> callbacks = Lists.newArrayList();
if (options.checkUnreachableCode.isOn()) {
callbacks.add(
new CheckUnreachableCode(compiler, options.checkUnreachableCode));
}
if (options.checkMissingReturn.isOn() && options.checkTypes) {
callbacks.add(
new CheckMissingReturn(compiler, options.checkMissingReturn));
}
return combineChecks(compiler, callbacks);
}
};
/** Checks access controls. Depends on type-inference. */
final HotSwapPassFactory checkAccessControls =
new HotSwapPassFactory("checkAccessControls", true) {
@Override
protected HotSwapCompilerPass create(AbstractCompiler compiler) {
return new CheckAccessControls(compiler);
}
};
/** Executes the given callbacks with a {@link CombinedCompilerPass}. */
private static HotSwapCompilerPass combineChecks(AbstractCompiler compiler,
List<Callback> callbacks) {
Preconditions.checkArgument(callbacks.size() > 0);
Callback[] array = callbacks.toArray(new Callback[callbacks.size()]);
return new CombinedCompilerPass(compiler, array);
}
/** A compiler pass that resolves types in the global scope. */
class GlobalTypeResolver implements HotSwapCompilerPass {
private final AbstractCompiler compiler;
GlobalTypeResolver(AbstractCompiler compiler) {
this.compiler = compiler;
}
@Override
public void process(Node externs, Node root) {
if (topScope == null) {
regenerateGlobalTypedScope(compiler, root.getParent());
} else {
compiler.getTypeRegistry().resolveTypesInScope(topScope);
}
}
@Override
public void hotSwapScript(Node scriptRoot, Node originalRoot) {
patchGlobalTypedScope(compiler, scriptRoot);
}
}
/** A compiler pass that clears the global scope. */
class ClearTypedScope implements
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>.
*
* @param callNode A CALL node.
*/
public SubclassRelationship getClassesDefinedByCall(Node callNode);
/**
* Returns true if passed a string referring to the superclass. The string
* will usually be from the string node at the right of a GETPROP, e.g.
* this.superClass_.
*/
public boolean isSuperClassReference(String propertyName);
/**
* Convenience method for determining provided dependencies amongst different
* JS scripts.
*/
public String extractClassNameIfProvide(Node node, Node parent);
/**
* Convenience method for determining required dependencies amongst different
* JS scripts.
*/
public String extractClassNameIfRequire(Node node, Node parent);
/**
* Function name used when exporting properties.
* Signature: fn(object, publicName, symbol).
* @return function name.
*/
public String getExportPropertyFunction();
/**
* Function name used when exporting symbols.
* Signature: fn(publicPath, object).
* @return function name.
*/
public String getExportSymbolFunction();
/**
* Checks if the given CALL node is forward-declaring any types,
* and returns the name of the types if it is.
*/
public List<String> identifyTypeDeclarationCall(Node n);
/**
* In many JS libraries, the function that produces inheritance also
* adds properties to the superclass and/or subclass.
*/
public void applySubclassRelationship(FunctionType parentCtor,
FunctionType childCtor, SubclassType type);
/**
* Function name for abstract methods. An abstract method can be assigned to
* an interface method instead of an function expression in order to avoid
* linter warnings produced by assigning a function without a return value
* where a return value is expected.
* @return function name.
*/
public String getAbstractMethodName();
/**
* Checks if the given method defines a singleton getter, and if it does,
* returns the name of the class with the singleton getter. By default, always
* returns null. Meant to be overridden by subclasses.
*
* addSingletonGetter needs a coding convention because in the general case,
* it can't be inlined. The function inliner sees that it creates an alias
* to the given class in an inner closure, and bails out.
*
*
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>;
public Bind(Node target, Node thisValue, Node parameters) {
this.target = target;
this.thisValue = thisValue;
this.parameters = parameters;
}
/**
* The number of parameters bound (not including the 'this' value).
*/
int getBoundParameterCount() {
if (parameters == null) {
return 0;
}
Node paramParent = parameters.getParent();
return paramParent.getChildCount() -
paramParent.getIndexOfChild(parameters);
}
}
/**
* Whether this CALL function is testing for the existence of a property.
*/
public boolean isPropertyTestFunction(Node call);
/**
* Whether this GETPROP node is an alias for an object prototype.
*/
public boolean isPrototypeAlias(Node getProp);
/**
* Checks if the given method performs a object literal cast, and if it does,
* returns information on the cast. By default, always returns null. Meant
* to be overridden by subclasses.
*
* @param callNode A CALL node.
*/
public ObjectLiteralCast getObjectLiteralCast(Node callNode);
/**
* Gets a collection of all properties that are defined indirectly on global
* objects. (For example, Closure defines superClass_ in the goog.inherits
* call).
*/
public Collection<String> getIndirectlyDeclaredProperties();
/**
* Returns the set of AssertionFunction.
*/
public Collection<AssertionFunctionSpec> getAssertionFunctions();
/** Specify the kind of inheritance */
static enum SubclassType {
INHERITS,
MIXIN
}
/** Record subclass relations */
static class SubclassRelationship {
final SubclassType type;
final String subclassName;
final String superclassName;
public SubclassRelationship(SubclassType type,
Node subclassNode, Node superclassNode) {
this.type = type;
this.subclassName = subclassNode.getQualifiedName();
this.superclassName = superclassNode.getQualifiedName();
}
}
/**
* Delegates provides a mechanism and structure for identifying where classes
* can call out to optional code to augment their functionality. The optional
* code is isolated from the base code through the use of a subclass in the
* optional code derived from the delegate class in the base code.
*/
static class DelegateRelationship {
/**
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>, not using another external package would
* limit our dependencies.
* <p>
* TODO(user): All functionality for removing nodes and edges.
*
*
* @param <N> Value type that the graph node stores.
* @param <E> Value type that the graph edge stores.
*/
public abstract class Graph<N, E> implements AdjacencyGraph<N, E> {
/**
* Pseudo typedef for a pair of annotations. Record of an object's
* annotation at some state.
*/
private static final class AnnotationState {
private final Annotatable first;
private final Annotation second;
public AnnotationState(Annotatable annotatable, Annotation annotation) {
this.first = annotatable;
this.second = annotation;
}
}
/**
* Pseudo typedef for ArrayList<AnnotationState>. Record of a collection of
* objects' annotations at some state.
*/
private static class GraphAnnotationState extends ArrayList<AnnotationState> {
private static final long serialVersionUID = 1L;
public GraphAnnotationState(int size) {
super(size);
}
}
/**
* Used by {@link #pushNodeAnnotations()} and {@link #popNodeAnnotations()}.
*/
private Deque<GraphAnnotationState> nodeAnnotationStack;
/**
* Used by {@link #pushEdgeAnnotations()} and {@link #popEdgeAnnotations()}.
*/
private Deque<GraphAnnotationState> edgeAnnotationStack;
/**
* Connects two nodes in the graph with an edge.
*
* @param n1 First node.
* @param edge The edge.
* @param n2 Second node.
*/
public abstract void connect(N n1, E edge, N n2);
/**
* Disconnects two nodes in the graph by removing all edges between them.
*
* @param n1 First node.
* @param n2 Second node.
*/
public abstract void disconnect(N n1, N n2);
/**
* Connects two nodes in the graph with an edge if such edge does not already
* exists between the nodes.
*
* @param n1 First node.
* @param edge The edge.
* @param n2 Second node.
*/
public final void connectIfNotFound(N n1, E edge, N n2) {
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> : haveAnnotations) {
stack.peek().add(new AnnotationState(h, h.getAnnotation()));
h.setAnnotation(null);
}
}
/**
* Restores the node annotations on the top of stack and pops stack.
*/
private static void popAnnotations(Deque<GraphAnnotationState> stack) {
for (AnnotationState as : stack.pop()) {
as.first.setAnnotation(as.second);
}
}
}
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Predicate;
import com.google.javascript.jscomp.ControlFlowGraph.Branch;
import com.google.javascript.jscomp.NodeTraversal.ScopedCallback;
import com.google.javascript.jscomp.graph.GraphNode;
import com.google.javascript.jscomp.graph.GraphReachability;
import com.google.javascript.jscomp.graph.GraphReachability.EdgeTuple;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.jstype.TernaryValue;
/**
* Use {@link ControlFlowGraph} and {@link GraphReachability} to inform user
* about unreachable code.
*
*/
class CheckUnreachableCode implements ScopedCallback {
static final DiagnosticType UNREACHABLE_CODE = DiagnosticType.error(
"JSC_UNREACHABLE_CODE", "unreachable code");
private final AbstractCompiler compiler;
private final CheckLevel level;
CheckUnreachableCode(AbstractCompiler compiler, CheckLevel level) {
this.compiler = compiler;
this.level = level;
}
@Override
public void enterScope(NodeTraversal t) {
initScope(t.getControlFlowGraph());
}
@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
GraphNode<Node, Branch> gNode = t.getControlFlowGraph().getNode(n);
if (gNode != null && gNode.getAnnotation() != GraphReachability.REACHABLE) {
// Only report error when there are
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> some line number informations.
// There are synthetic nodes with no line number informations, nodes
// introduce by other passes (although not likely since this pass should
// be executed early) or some rhino bug.
if (n.getLineno() != -1 &&
// Allow spurious semi-colons and spurious breaks.
!n.isEmpty() && !n.isBreak()) {
compiler.report(t.makeError(n, level, UNREACHABLE_CODE));
// From now on, we are going to assume the user fixed the error and not
// give more warning related to code section reachable from this node.
new GraphReachability<Node, ControlFlowGraph.Branch>(
t.getControlFlowGraph()).recompute(n);
// Saves time by not traversing children.
return false;
}
}
return true;
}
private void initScope(ControlFlowGraph<Node> controlFlowGraph) {
new GraphReachability<Node, ControlFlowGraph.Branch>(
controlFlowGraph, new ReachablePredicate()).compute(
controlFlowGraph.getEntry().getValue());
}
@Override
public void exitScope(NodeTraversal t) {
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
}
private final class ReachablePredicate implements
Predicate<EdgeTuple<Node, ControlFlowGraph.Branch>> {
@Override
public boolean apply(EdgeTuple<Node, Branch> input) {
Branch branch = input.edge;
if (!branch.isConditional()) {
return true;
}
Node predecessor = input.sourceNode;
Node condition = NodeUtil.getConditionExpression(predecessor);
// TODO(user): Handle more complicated expression like true == true,
// etc....
if (condition != null) {
TernaryValue val = NodeUtil.getImpureBooleanValue(condition);
if (val != TernaryValue.UNKNOWN) {
return val.toBoolean(true) == (branch == Branch.ON_TRUE);
}
}
return true;
}
}
}
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>/*
* Copyright 2012 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
/**
* Checks for common errors, such as misplaced semicolons:
* <pre>
* if (x); act_now();
* </pre>
* or comparison against NaN:
* <pre>
* if (x === NaN) act();
* </pre>
* and generates warnings.
*
* @author johnlenz@google.com (John Lenz)
*/
final class CheckSuspiciousCode extends AbstractPostOrderCallback {
static final DiagnosticType SUSPICIOUS_SEMICOLON = DiagnosticType.warning(
"JSC_SUSPICIOUS_SEMICOLON",
"If this if/for/while really shouldn't have a body, use {}");
static final DiagnosticType SUSPICIOUS_COMPARISON_WITH_NAN =
DiagnosticType.warning(
"JSC_SUSPICIOUS_NAN",
"Comparison again NaN is always false. Did you mean isNaN()?");
CheckSuspiciousCode() {
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
checkMissingSemicolon(t, n);
checkNaN(t, n);
}
private void checkMissingSemicolon(NodeTraversal t, Node n) {
switch (n.getType()) {
case Token.IF:
Node true
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>Case = n.getFirstChild().getNext();
reportIfWasEmpty(t, trueCase);
Node elseCase = trueCase.getNext();
if (elseCase != null) {
reportIfWasEmpty(t, elseCase);
}
break;
case Token.WHILE:
case Token.FOR:
reportIfWasEmpty(t, NodeUtil.getLoopCodeBlock(n));
break;
}
}
private void reportIfWasEmpty(NodeTraversal t, Node block) {
Preconditions.checkState(block.isBlock());
// A semicolon is distinguished from a block without children by
// annotating it with EMPTY_BLOCK. Blocks without children are
// usually intentional, especially with loops.
if (!block.hasChildren() && block.wasEmptyNode()) {
t.getCompiler().report(
t.makeError(block, SUSPICIOUS_SEMICOLON));
}
}
private void checkNaN(NodeTraversal t, Node n) {
switch (n.getType()) {
case Token.EQ:
case Token.GE:
case Token.GT:
case Token.LE:
case Token.LT:
case Token.NE:
case Token.SHEQ:
case Token.SHNE:
reportIfNaN(t, n.getFirstChild());
reportIfNaN(t, n.getLastChild());
}
}
private void reportIfNaN(NodeTraversal t, Node n) {
if (NodeUtil.isNaN(n)) {
t.getCompiler().report(
t.makeError(n.getParent(), SUSPICIOUS_COMPARISON_WITH_NAN));
}
}
}
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> new DepsFinder();
Node root = getAstRoot(compiler);
if (root == null) {
return;
}
finder.visitTree(getAstRoot(compiler));
// TODO(nicksantos|user): This caching behavior is a bit
// odd, and only works if you assume the exact call flow that
// clients are currently using. In that flow, they call
// getProvides(), then remove the goog.provide calls from the
// AST, and then call getProvides() again.
//
// This won't work for any other call flow, or any sort of incremental
// compilation scheme. The API needs to be fixed so callers aren't
// doing weird things like this, and then we should get rid of the
// multiple-scan strategy.
provides.addAll(finder.provides);
requires.addAll(finder.requires);
} else {
// Otherwise, look at the source code.
if (!generatedDependencyInfoFromSource) {
// Note: it's OK to use getName() instead of
// getPathRelativeToClosureBase() here because we're not using
// this to generate deps files. (We're only using it for
// symbol dependencies.)
DependencyInfo info =
(new JsFileParser(compiler.getErrorManager()))
.setIncludeGoogBase(true)
.parseFile(getName(), getName(), getCode());
provides.addAll(info.getProvides());
requires.addAll(info.getRequires());
generatedDependencyInfoFromSource = true;
}
}
}
private static class DepsFinder {
private final List<String> provides = Lists.newArrayList();
private final List<String> requires = Lists.newArrayList();
private final CodingConvention codingConvention =
new ClosureCodingConvention();
void visitTree(Node n) {
visitSubtree(n, null);
}
void visitSubtree(Node n, Node parent) {
if (n.isCall()) {
String require =
codingConvention.extractClassNameIfRequire(n, parent);
if (require != null) {
requires.add(require);
}
String provide =
codingConvention.extractClassNameIfProvide(n, parent);
if (provide != null) {
provides.add(provide);
}
return;
} else if (parent != null &&
!parent.isExpr
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>Result() &&
!parent.isScript()) {
return;
}
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
visitSubtree(child, n);
}
}
}
/**
* Gets the source line for the indicated line number.
*
* @param lineNumber the line number, 1 being the first line of the file.
* @return The line indicated. Does not include the newline at the end
* of the file. Returns {@code null} if it does not exist,
* or if there was an IO exception.
*/
public String getLine(int lineNumber) {
return getSourceFile().getLine(lineNumber);
}
/**
* Get a region around the indicated line number. The exact definition of a
* region is implementation specific, but it must contain the line indicated
* by the line number. A region must not start or end by a carriage return.
*
* @param lineNumber the line number, 1 being the first line of the file.
* @return The line indicated. Returns {@code null} if it does not exist,
* or if there was an IO exception.
*/
public Region getRegion(int lineNumber) {
return getSourceFile().getRegion(lineNumber);
}
public String getCode() throws IOException {
return getSourceFile().getCode();
}
/** Returns the module to which the input belongs. */
public JSModule getModule() {
return module;
}
/** Sets the module to which the input belongs. */
public void setModule(JSModule module) {
// An input may only belong to one module.
Preconditions.checkArgument(
module == null || this.module == null || this.module == module);
this.module = module;
}
/** Overrides the module to which the input belongs. */
void overrideModule(JSModule module) {
this.module = module;
}
public boolean isExtern() {
if (ast == null || ast.getSourceFile() == null) {
return false;
}
return ast.getSourceFile().isExtern();
}
void setIsExtern(boolean isExtern) {
if (ast == null || ast.getSourceFile() == null) {
return;
}
ast.getSourceFile().
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>.jstype.EnumElementType;
import com.google.javascript.rhino.jstype.FunctionType;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.JSTypeNative;
import com.google.javascript.rhino.jstype.JSTypeRegistry;
import com.google.javascript.rhino.jstype.ObjectType;
import com.google.javascript.rhino.jstype.StaticSlot;
import com.google.javascript.rhino.jstype.TemplateType;
import com.google.javascript.rhino.jstype.TemplatizedType;
import com.google.javascript.rhino.jstype.UnionType;
import com.google.javascript.rhino.jstype.Visitor;
/**
* Chainable reverse abstract interpreter providing basic functionality.
*
*/
public abstract class ChainableReverseAbstractInterpreter
implements ReverseAbstractInterpreter {
protected final CodingConvention convention;
final JSTypeRegistry typeRegistry;
private ChainableReverseAbstractInterpreter firstLink;
private ChainableReverseAbstractInterpreter nextLink;
/**
* Constructs an interpreter, which is the only link in a chain. Interpreters
* can be appended using {@link #append}.
*/
public ChainableReverseAbstractInterpreter(CodingConvention convention,
JSTypeRegistry typeRegistry) {
Preconditions.checkNotNull(convention);
this.convention = convention;
this.typeRegistry = typeRegistry;
firstLink = this;
nextLink = null;
}
/**
* Appends a link to {@code this}, returning the updated last link.
* <p>
* The pattern {@code new X().append(new Y())...append(new Z())} forms a
* chain starting with X, then Y, then ... Z.
* @param lastLink a chainable interpreter, with no next link
* @return the updated last link
*/
public ChainableReverseAbstractInterpreter append(
ChainableReverseAbstractInterpreter lastLink) {
Preconditions.checkArgument(lastLink.nextLink == null);
this.nextLink = lastLink;
lastLink.firstLink = this.firstLink;
return lastLink;
}
/**
* Gets the first link of this chain.
*/
public ChainableReverseAbstractInterpreter getFirst() {
return
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>/*
* Copyright 2010 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.collect.Maps;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.Map;
/**
* Filters warnings based on in-code {@code @suppress} annotations.
* @author nicksantos@google.com (Nick Santos)
*/
class SuppressDocWarningsGuard extends WarningsGuard {
private static final long serialVersionUID = 1L;
/** Warnings guards for each suppressible warnings group, indexed by name. */
private final Map<String, DiagnosticGroupWarningsGuard> suppressors =
Maps.newHashMap();
/**
* The suppressible groups, indexed by name.
*/
SuppressDocWarningsGuard(Map<String, DiagnosticGroup> suppressibleGroups) {
for (Map.Entry<String, DiagnosticGroup> entry :
suppressibleGroups.entrySet()) {
suppressors.put(
entry.getKey(),
new DiagnosticGroupWarningsGuard(
entry.getValue(),
CheckLevel.OFF));
}
}
@Override
public CheckLevel level(JSError error) {
Node node = error.node;
if (node != null) {
boolean visitedFunction = false;
for (Node current = node;
current != null;
current = current.getParent()) {
int type = current.getType();
JSDocInfo info = null;
if (type == Token.FUNCTION) {
info = NodeUtil.getBestJSDocInfo(current);
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>
visitedFunction = true;
} else if (type == Token.SCRIPT) {
info = current.getJSDocInfo();
} else if (current.isVar() || current.isAssign()) {
// There's one edge case we're worried about:
// if the warning points to an assigment to a function, we
// want the suppressions on that function to apply.
// It's OK if we double-count some cases.
Node rhs = NodeUtil.getRValueOfLValue(current.getFirstChild());
if (rhs != null) {
if (rhs.isCast()) {
rhs = rhs.getFirstChild();
}
if (rhs.isFunction() && !visitedFunction) {
info = NodeUtil.getBestJSDocInfo(current);
}
}
}
if (info != null) {
for (String suppressor : info.getSuppressions()) {
WarningsGuard guard = suppressors.get(suppressor);
// Some @suppress tags are for other tools, and
// may not have a warnings guard.
if (guard != null) {
CheckLevel newLevel = guard.level(error);
if (newLevel != null) {
return newLevel;
}
}
}
}
}
}
return null;
}
@Override
public int getPriority() {
// Happens after path-based filtering, but before other times
// of filtering.
return WarningsGuard.Priority.SUPPRESS_DOC.value;
}
}
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>WithStatement(WithStatement statementNode);
abstract T processIllegalToken(AstNode node);
public T process(AstNode node) {
switch (node.getType()) {
case Token.ADD:
case Token.AND:
case Token.BITAND:
case Token.BITOR:
case Token.BITXOR:
case Token.COMMA:
case Token.DIV:
case Token.EQ:
case Token.GE:
case Token.GT:
case Token.IN:
case Token.INSTANCEOF:
case Token.LE:
case Token.LSH:
case Token.LT:
case Token.MOD:
case Token.MUL:
case Token.NE:
case Token.OR:
case Token.RSH:
case Token.SHEQ:
case Token.SHNE:
case Token.SUB:
case Token.URSH:
return processInfixExpression((InfixExpression) node);
case Token.ARRAYLIT:
return processArrayLiteral((ArrayLiteral) node);
case Token.ASSIGN:
case Token.ASSIGN_ADD:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_DIV:
case Token.ASSIGN_LSH:
case Token.ASSIGN_MOD:
case Token.ASSIGN_MUL:
case Token.ASSIGN_RSH:
case Token.ASSIGN_SUB:
case Token.ASSIGN_URSH:
return processAssignment((Assignment) node);
case Token.BITNOT:
case Token.DEC:
case Token.DELPROP:
case Token.INC:
case Token.NEG:
case Token.NOT:
case Token.POS:
case Token.TYPEOF:
case Token.VOID:
return processUnaryExpression((UnaryExpression) node);
case Token.BLOCK:
if (node instanceof Block) {
return processBlock((Block) node);
} else if (node instanceof Scope) {
return processScope((Scope) node);
} else {
throw new IllegalStateException("Unexpected node type. class: " +
node.getClass() +
" type: " +
Token.typeToName(node.getType()));
}
case Token.BREAK:
return processBreakStatement((BreakStatement) node);
case Token.CALL:
return
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>ARGEST_BASIC_LATIN = 0x7f;
/** the set of builtin constructors that don't have side effects. */
private static final Set<String> CONSTRUCTORS_WITHOUT_SIDE_EFFECTS =
new HashSet<String>(Arrays.asList(
"Array",
"Date",
"Error",
"Object",
"RegExp",
"XMLHttpRequest"));
// Utility class; do not instantiate.
private NodeUtil() {}
/**
* Gets the boolean value of a node that represents a expression. This method
* effectively emulates the <code>Boolean()</code> JavaScript cast function.
* Note: unlike getBooleanValue this function does not return UNKNOWN
* for expressions with side-effects.
*/
static TernaryValue getImpureBooleanValue(Node n) {
switch (n.getType()) {
case Token.ASSIGN:
case Token.COMMA:
// For ASSIGN and COMMA the value is the value of the RHS.
return getImpureBooleanValue(n.getLastChild());
case Token.NOT:
TernaryValue value = getImpureBooleanValue(n.getLastChild());
return value.not();
case Token.AND: {
TernaryValue lhs = getImpureBooleanValue(n.getFirstChild());
TernaryValue rhs = getImpureBooleanValue(n.getLastChild());
return lhs.and(rhs);
}
case Token.OR: {
TernaryValue lhs = getImpureBooleanValue(n.getFirstChild());
TernaryValue rhs = getImpureBooleanValue(n.getLastChild());
return lhs.or(rhs);
}
case Token.HOOK: {
TernaryValue trueValue = getImpureBooleanValue(
n.getFirstChild().getNext());
TernaryValue falseValue = getImpureBooleanValue(n.getLastChild());
if (trueValue.equals(falseValue)) {
return trueValue;
} else {
return TernaryValue.UNKNOWN;
}
}
case Token.ARRAYLIT:
case Token.OBJECTLIT:
// ignoring side-effects
return TernaryValue.TRUE;
case Token.VOID:
return TernaryValue.FALSE;
default:
return getPureBooleanValue(n);
}
}
/**
* Gets the boolean value of a node that represents a literal
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>) == Character.SPACE_SEPARATOR)
? TernaryValue.TRUE : TernaryValue.FALSE;
}
}
/**
* Gets the function's name. This method recognizes five forms:
* <ul>
* <li>{@code function name() ...}</li>
* <li>{@code var name = function() ...}</li>
* <li>{@code qualified.name = function() ...}</li>
* <li>{@code var name2 = function name1() ...}</li>
* <li>{@code qualified.name2 = function name1() ...}</li>
* </ul>
* In two last cases with named function expressions, the second name is
* returned (the variable of qualified name).
*
* @param n a node whose type is {@link Token#FUNCTION}
* @return the function's name, or {@code null} if it has no name
*/
static String getFunctionName(Node n) {
Preconditions.checkState(n.isFunction());
Node parent = n.getParent();
switch (parent.getType()) {
case Token.NAME:
// var name = function() ...
// var name2 = function name1() ...
return parent.getQualifiedName();
case Token.ASSIGN:
// qualified.name = function() ...
// qualified.name2 = function name1() ...
return parent.getFirstChild().getQualifiedName();
default:
// function name() ...
String name = n.getFirstChild().getQualifiedName();
return name;
}
}
/**
* Gets the function's name. This method recognizes the forms:
* <ul>
* <li>{@code {'name': function() ...}}</li>
* <li>{@code {name: function() ...}}</li>
* <li>{@code function name() ...}</li>
* <li>{@code var name = function() ...}</li>
* <li>{@code qualified.name = function() ...}</li>
* <li>{@code var name2 = function name1() ...}</li>
* <li>{@code qualified.name2 = function name1() ...}</li>
* </ul>
*
* @param n a node whose type is {@link Token#FUNCTION}
* @
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>return the function's name, or {@code null} if it has no name
*/
public static String getNearestFunctionName(Node n) {
if (!n.isFunction()) {
return null;
}
String name = getFunctionName(n);
if (name != null) {
return name;
}
// Check for the form { 'x' : function() { } }
Node parent = n.getParent();
switch (parent.getType()) {
case Token.SETTER_DEF:
case Token.GETTER_DEF:
case Token.STRING_KEY:
// Return the name of the literal's key.
return parent.getString();
case Token.NUMBER:
return getStringValue(parent);
}
return null;
}
/**
* Returns true if this is an immutable value.
*/
static boolean isImmutableValue(Node n) {
switch (n.getType()) {
case Token.STRING:
case Token.NUMBER:
case Token.NULL:
case Token.TRUE:
case Token.FALSE:
return true;
case Token.CAST:
case Token.NOT:
return isImmutableValue(n.getFirstChild());
case Token.VOID:
case Token.NEG:
return isImmutableValue(n.getFirstChild());
case Token.NAME:
String name = n.getString();
// We assume here that programs don't change the value of the keyword
// undefined to something other than the value undefined.
return "undefined".equals(name)
|| "Infinity".equals(name)
|| "NaN".equals(name);
}
return false;
}
/**
* Returns true if the operator on this node is symmetric
*/
static boolean isSymmetricOperation(Node n) {
switch (n.getType()) {
case Token.EQ: // equal
case Token.NE: // not equal
case Token.SHEQ: // exactly equal
case Token.SHNE: // exactly not equal
case Token.MUL: // multiply, unlike add it only works on numbers
// or results NaN if any of the operators is not a number
return true;
}
return false;
}
/**
* Returns true if the operator on this node is relational.
* the returned set does not include the equalities.
*/
static boolean isRelationalOperation(Node n) {
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> child.getNext()) {
if (!isLiteralValue(child, includeFunctions)) {
return false;
}
}
return true;
case Token.OBJECTLIT:
// Return true only if all values are const.
for (Node child = n.getFirstChild(); child != null;
child = child.getNext()) {
if (!isLiteralValue(child.getFirstChild(), includeFunctions)) {
return false;
}
}
return true;
case Token.FUNCTION:
return includeFunctions && !NodeUtil.isFunctionDeclaration(n);
default:
return isImmutableValue(n);
}
}
/**
* Determines whether the given value may be assigned to a define.
*
* @param val The value being assigned.
* @param defines The list of names of existing defines.
*/
static boolean isValidDefineValue(Node val, Set<String> defines) {
switch (val.getType()) {
case Token.STRING:
case Token.NUMBER:
case Token.TRUE:
case Token.FALSE:
return true;
// Binary operators are only valid if both children are valid.
case Token.ADD:
case Token.BITAND:
case Token.BITNOT:
case Token.BITOR:
case Token.BITXOR:
case Token.DIV:
case Token.EQ:
case Token.GE:
case Token.GT:
case Token.LE:
case Token.LSH:
case Token.LT:
case Token.MOD:
case Token.MUL:
case Token.NE:
case Token.RSH:
case Token.SHEQ:
case Token.SHNE:
case Token.SUB:
case Token.URSH:
return isValidDefineValue(val.getFirstChild(), defines)
&& isValidDefineValue(val.getLastChild(), defines);
// Unary operators are valid if the child is valid.
case Token.NOT:
case Token.NEG:
case Token.POS:
return isValidDefineValue(val.getFirstChild(), defines);
// Names are valid if and only if they are defines themselves.
case Token.NAME:
case Token.GETPROP:
if (val.isQualifiedName()) {
return defines.contains(val.getQualifiedName());
}
}
return false;
}
/**
* Returns whether this a BLOCK node with
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> no children.
*
* @param block The node.
*/
static boolean isEmptyBlock(Node block) {
if (!block.isBlock()) {
return false;
}
for (Node n = block.getFirstChild(); n != null; n = n.getNext()) {
if (!n.isEmpty()) {
return false;
}
}
return true;
}
static boolean isSimpleOperator(Node n) {
return isSimpleOperatorType(n.getType());
}
/**
* A "simple" operator is one whose children are expressions,
* has no direct side-effects (unlike '+='), and has no
* conditional aspects (unlike '||').
*/
static boolean isSimpleOperatorType(int type) {
switch (type) {
case Token.ADD:
case Token.BITAND:
case Token.BITNOT:
case Token.BITOR:
case Token.BITXOR:
case Token.COMMA:
case Token.DIV:
case Token.EQ:
case Token.GE:
case Token.GETELEM:
case Token.GETPROP:
case Token.GT:
case Token.INSTANCEOF:
case Token.LE:
case Token.LSH:
case Token.LT:
case Token.MOD:
case Token.MUL:
case Token.NE:
case Token.NOT:
case Token.RSH:
case Token.SHEQ:
case Token.SHNE:
case Token.SUB:
case Token.TYPEOF:
case Token.VOID:
case Token.POS:
case Token.NEG:
case Token.URSH:
return true;
default:
return false;
}
}
/**
* Creates an EXPR_RESULT.
*
* @param child The expression itself.
* @return Newly created EXPR node with the child as subexpression.
*/
static Node newExpr(Node child) {
return IR.exprResult(child).srcref(child);
}
/**
* Returns true if the node may create new mutable state, or change existing
* state.
*
* @see <a href="http://www.xkcd.org/326/">XKCD Cartoon</a>
*/
static boolean mayEffectMutableState(Node n) {
return may
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>EffectMutableState(n, null);
}
static boolean mayEffectMutableState(Node n, AbstractCompiler compiler) {
return checkForStateChangeHelper(n, true, compiler);
}
/**
* Returns true if the node which may have side effects when executed.
*/
static boolean mayHaveSideEffects(Node n) {
return mayHaveSideEffects(n, null);
}
static boolean mayHaveSideEffects(Node n, AbstractCompiler compiler) {
return checkForStateChangeHelper(n, false, compiler);
}
/**
* Returns true if some node in n's subtree changes application state.
* If {@code checkForNewObjects} is true, we assume that newly created
* mutable objects (like object literals) change state. Otherwise, we assume
* that they have no side effects.
*/
private static boolean checkForStateChangeHelper(
Node n, boolean checkForNewObjects, AbstractCompiler compiler) {
// Rather than id which ops may have side effects, id the ones
// that we know to be safe
switch (n.getType()) {
// other side-effect free statements and expressions
case Token.CAST:
case Token.AND:
case Token.BLOCK:
case Token.EXPR_RESULT:
case Token.HOOK:
case Token.IF:
case Token.IN:
case Token.PARAM_LIST:
case Token.NUMBER:
case Token.OR:
case Token.THIS:
case Token.TRUE:
case Token.FALSE:
case Token.NULL:
case Token.STRING:
case Token.STRING_KEY:
case Token.SWITCH:
case Token.TRY:
case Token.EMPTY:
break;
// Throws are by definition side effects
case Token.THROW:
return true;
case Token.OBJECTLIT:
if (checkForNewObjects) {
return true;
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (checkForStateChangeHelper(
c.getFirstChild(), checkForNewObjects, compiler)) {
return true;
}
}
return false;
case Token.ARRAYLIT:
case Token.REGEXP:
if (checkForNewObjects) {
return true;
}
break;
case Token.VAR: // empty var statement (no declaration
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> * 10 bitwise shift << >> >>>
* 11 addition/subtraction + -
* 12 multiply/divide * / %
* 13 negation/increment ! ~ - ++ --
* 14 call, member () [] .
*/
static int precedence(int type) {
int precedence = precedenceWithDefault(type);
if (precedence != -1) {
return precedence;
}
throw new Error("Unknown precedence for " +
Token.name(type) + " (type " + type + ")");
}
static int precedenceWithDefault(int type) {
switch (type) {
case Token.COMMA: return 0;
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.ASSIGN_URSH:
case Token.ASSIGN_ADD:
case Token.ASSIGN_SUB:
case Token.ASSIGN_MUL:
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
case Token.ASSIGN: return 1;
case Token.HOOK: return 2; // ?: operator
case Token.OR: return 3;
case Token.AND: return 4;
case Token.BITOR: return 5;
case Token.BITXOR: return 6;
case Token.BITAND: return 7;
case Token.EQ:
case Token.NE:
case Token.SHEQ:
case Token.SHNE: return 8;
case Token.LT:
case Token.GT:
case Token.LE:
case Token.GE:
case Token.INSTANCEOF:
case Token.IN: return 9;
case Token.LSH:
case Token.RSH:
case Token.URSH: return 10;
case Token.SUB:
case Token.ADD: return 11;
case Token.MUL:
case Token.MOD:
case Token.DIV: return 12;
case Token.INC:
case Token.DEC:
case Token.NEW:
case Token.DELPROP:
case Token.TYPEOF:
case Token.VOID:
case Token.NOT:
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>
case Token.BITNOT:
case Token.POS:
case Token.NEG: return 13;
case Token.CALL:
case Token.GETELEM:
case Token.GETPROP:
// Data values
case Token.ARRAYLIT:
case Token.EMPTY: // TODO(johnlenz): remove this.
case Token.FALSE:
case Token.FUNCTION:
case Token.NAME:
case Token.NULL:
case Token.NUMBER:
case Token.OBJECTLIT:
case Token.REGEXP:
case Token.STRING:
case Token.STRING_KEY:
case Token.THIS:
case Token.TRUE:
return 15;
case Token.CAST:
return 16;
default:
// Statements are lower precedence than expressions.
return -1;
}
}
static boolean isUndefined(Node n) {
switch (n.getType()) {
case Token.VOID:
return true;
case Token.NAME:
return n.getString().equals("undefined");
}
return false;
}
static boolean isNullOrUndefined(Node n) {
return n.isNull() || isUndefined(n);
}
static final Predicate<Node> IMMUTABLE_PREDICATE = new Predicate<Node>() {
@Override
public boolean apply(Node n) {
return isImmutableValue(n);
}
};
static boolean isImmutableResult(Node n) {
return allResultsMatch(n, IMMUTABLE_PREDICATE);
}
/**
* Apply the supplied predicate against
* all possible result Nodes of the expression.
*/
static boolean allResultsMatch(Node n, Predicate<Node> p) {
switch (n.getType()) {
case Token.CAST:
return allResultsMatch(n.getFirstChild(), p);
case Token.ASSIGN:
case Token.COMMA:
return allResultsMatch(n.getLastChild(), p);
case Token.AND:
case Token.OR:
return allResultsMatch(n.getFirstChild(), p)
&& allResultsMatch(n.getLastChild(), p);
case Token.HOOK:
return allResultsMatch(n.getFirstChild().getNext(), p)
&& allResultsMatch(n.getLastChild(), p);
default:
return p.apply(n
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>);
}
}
/**
* Apply the supplied predicate against
* all possible result Nodes of the expression.
*/
static boolean anyResultsMatch(Node n, Predicate<Node> p) {
switch (n.getType()) {
case Token.CAST:
return anyResultsMatch(n.getFirstChild(), p);
case Token.ASSIGN:
case Token.COMMA:
return anyResultsMatch(n.getLastChild(), p);
case Token.AND:
case Token.OR:
return anyResultsMatch(n.getFirstChild(), p)
|| anyResultsMatch(n.getLastChild(), p);
case Token.HOOK:
return anyResultsMatch(n.getFirstChild().getNext(), p)
|| anyResultsMatch(n.getLastChild(), p);
default:
return p.apply(n);
}
}
static class NumbericResultPredicate implements Predicate<Node> {
@Override
public boolean apply(Node n) {
return isNumericResultHelper(n);
}
}
static final NumbericResultPredicate NUMBERIC_RESULT_PREDICATE =
new NumbericResultPredicate();
/**
* Returns true if the result of node evaluation is always a number
*/
static boolean isNumericResult(Node n) {
return allResultsMatch(n, NUMBERIC_RESULT_PREDICATE);
}
static boolean isNumericResultHelper(Node n) {
switch (n.getType()) {
case Token.ADD:
return !mayBeString(n.getFirstChild())
&& !mayBeString(n.getLastChild());
case Token.BITNOT:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
case Token.LSH:
case Token.RSH:
case Token.URSH:
case Token.SUB:
case Token.MUL:
case Token.MOD:
case Token.DIV:
case Token.INC:
case Token.DEC:
case Token.POS:
case Token.NEG:
case Token.NUMBER:
return true;
case Token.NAME:
String name = n.getString();
if (name.equals("NaN")) {
return true;
}
if (name.equals("Infinity")) {
return true;
}
return false;
default:
return false;
}
}
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> strings. e.g. "a" + (1 + 2) is not "a" + 1 + 2
*/
static boolean isAssociative(int type) {
switch (type) {
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return true;
default:
return false;
}
}
/**
* Returns true if the operator is commutative.
* e.g. (a * b) * c = c * (b * a)
* Note 1: "+" is not commutative because it is also the concatenation
* for strings. e.g. "a" + (1 + 2) is not "a" + 1 + 2
* Note 2: only operations on literals and pure functions are commutative.
*/
static boolean isCommutative(int type) {
switch (type) {
case Token.MUL:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return true;
default:
return false;
}
}
static boolean isAssignmentOp(Node n) {
switch (n.getType()){
case Token.ASSIGN:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.ASSIGN_URSH:
case Token.ASSIGN_ADD:
case Token.ASSIGN_SUB:
case Token.ASSIGN_MUL:
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
return true;
}
return false;
}
static int getOpFromAssignmentOp(Node n) {
switch (n.getType()){
case Token.ASSIGN_BITOR:
return Token.BITOR;
case Token.ASSIGN_BITXOR:
return Token.BITXOR;
case Token.ASSIGN_BITAND:
return Token.BITAND;
case Token.ASSIGN_LSH:
return Token.LSH;
case Token.ASSIGN_RSH:
return Token.RSH;
case Token.ASSIGN_URSH:
return Token.UR
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>SH;
case Token.ASSIGN_ADD:
return Token.ADD;
case Token.ASSIGN_SUB:
return Token.SUB;
case Token.ASSIGN_MUL:
return Token.MUL;
case Token.ASSIGN_DIV:
return Token.DIV;
case Token.ASSIGN_MOD:
return Token.MOD;
}
throw new IllegalArgumentException("Not an assignment op:" + n);
}
/**
* Determines if the given node contains a function statement or function
* expression.
*/
static boolean containsFunction(Node n) {
return containsType(n, Token.FUNCTION);
}
/**
* Returns true if the shallow scope contains references to 'this' keyword
*/
static boolean referencesThis(Node n) {
Node start = (n.isFunction()) ? n.getLastChild() : n;
return containsType(start, Token.THIS, MATCH_NOT_FUNCTION);
}
/**
* Is this a GETPROP or GETELEM node?
*/
static boolean isGet(Node n) {
return n.isGetProp() || n.isGetElem();
}
/**
* Is this node the name of a variable being declared?
*
* @param n The node
* @return True if {@code n} is NAME and {@code parent} is VAR
*/
static boolean isVarDeclaration(Node n) {
// There is no need to verify that parent != null because a NAME node
// always has a parent in a valid parse tree.
return n.isName() && n.getParent().isVar();
}
/**
* For an assignment or variable declaration get the assigned value.
* @return The value node representing the new value.
*/
static Node getAssignedValue(Node n) {
Preconditions.checkState(n.isName());
Node parent = n.getParent();
if (parent.isVar()) {
return n.getFirstChild();
} else if (parent.isAssign() && parent.getFirstChild() == n) {
return n.getNext();
} else {
return null;
}
}
/**
* Is this node an assignment expression statement?
*
* @param n The node
* @return True if {@code n} is EXPR_RESULT and {@code n}'s
* first child is ASSIGN
*/
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> static boolean isExprAssign(Node n) {
return n.isExprResult()
&& n.getFirstChild().isAssign();
}
/**
* Is this node a call expression statement?
*
* @param n The node
* @return True if {@code n} is EXPR_RESULT and {@code n}'s
* first child is CALL
*/
static boolean isExprCall(Node n) {
return n.isExprResult()
&& n.getFirstChild().isCall();
}
/**
* @return Whether the node represents a FOR-IN loop.
*/
static boolean isForIn(Node n) {
return n.isFor()
&& n.getChildCount() == 3;
}
/**
* Determines whether the given node is a FOR, DO, or WHILE node.
*/
static boolean isLoopStructure(Node n) {
switch (n.getType()) {
case Token.FOR:
case Token.DO:
case Token.WHILE:
return true;
default:
return false;
}
}
/**
* @param n The node to inspect.
* @return If the node, is a FOR, WHILE, or DO, it returns the node for
* the code BLOCK, null otherwise.
*/
static Node getLoopCodeBlock(Node n) {
switch (n.getType()) {
case Token.FOR:
case Token.WHILE:
return n.getLastChild();
case Token.DO:
return n.getFirstChild();
default:
return null;
}
}
/**
* @return Whether the specified node has a loop parent that
* is within the current scope.
*/
static boolean isWithinLoop(Node n) {
for (Node parent : n.getAncestors()) {
if (NodeUtil.isLoopStructure(parent)) {
return true;
}
if (parent.isFunction()) {
break;
}
}
return false;
}
/**
* Determines whether the given node is a FOR, DO, WHILE, WITH, or IF node.
*/
static boolean isControlStructure(Node n) {
switch (n.getType()) {
case Token.FOR:
case Token.DO:
case Token.WHILE:
case Token.WITH:
case Token.
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>IF:
case Token.LABEL:
case Token.TRY:
case Token.CATCH:
case Token.SWITCH:
case Token.CASE:
case Token.DEFAULT_CASE:
return true;
default:
return false;
}
}
/**
* Determines whether the given node is code node for FOR, DO,
* WHILE, WITH, or IF node.
*/
static boolean isControlStructureCodeBlock(Node parent, Node n) {
switch (parent.getType()) {
case Token.FOR:
case Token.WHILE:
case Token.LABEL:
case Token.WITH:
return parent.getLastChild() == n;
case Token.DO:
return parent.getFirstChild() == n;
case Token.IF:
return parent.getFirstChild() != n;
case Token.TRY:
return parent.getFirstChild() == n || parent.getLastChild() == n;
case Token.CATCH:
return parent.getLastChild() == n;
case Token.SWITCH:
case Token.CASE:
return parent.getFirstChild() != n;
case Token.DEFAULT_CASE:
return true;
default:
Preconditions.checkState(isControlStructure(parent));
return false;
}
}
/**
* Gets the condition of an ON_TRUE / ON_FALSE CFG edge.
* @param n a node with an outgoing conditional CFG edge
* @return the condition node or null if the condition is not obviously a node
*/
static Node getConditionExpression(Node n) {
switch (n.getType()) {
case Token.IF:
case Token.WHILE:
return n.getFirstChild();
case Token.DO:
return n.getLastChild();
case Token.FOR:
switch (n.getChildCount()) {
case 3:
return null;
case 4:
return n.getFirstChild().getNext();
}
throw new IllegalArgumentException("malformed 'for' statement " + n);
case Token.CASE:
return null;
}
throw new IllegalArgumentException(n + " does not have a condition.");
}
/**
* @return Whether the node is of a type that contain other statements.
*/
static boolean isStatementBlock(Node n) {
return n.isScript() || n.isBlock();
}
/**
*
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> @return Whether the node is used as a statement.
*/
static boolean isStatement(Node n) {
return isStatementParent(n.getParent());
}
static boolean isStatementParent(Node parent) {
// It is not possible to determine definitely if a node is a statement
// or not if it is not part of the AST. A FUNCTION node can be
// either part of an expression or a statement.
Preconditions.checkState(parent != null);
switch (parent.getType()) {
case Token.SCRIPT:
case Token.BLOCK:
case Token.LABEL:
return true;
default:
return false;
}
}
/** Whether the node is part of a switch statement. */
static boolean isSwitchCase(Node n) {
return n.isCase() || n.isDefaultCase();
}
/**
* @return Whether the name is a reference to a variable, function or
* function parameter (not a label or a empty function expression name).
*/
static boolean isReferenceName(Node n) {
return n.isName() && !n.getString().isEmpty();
}
/** Whether the child node is the FINALLY block of a try. */
static boolean isTryFinallyNode(Node parent, Node child) {
return parent.isTry() && parent.getChildCount() == 3
&& child == parent.getLastChild();
}
/** Whether the node is a CATCH container BLOCK. */
static boolean isTryCatchNodeContainer(Node n) {
Node parent = n.getParent();
return parent.isTry()
&& parent.getFirstChild().getNext() == n;
}
/** Safely remove children while maintaining a valid node structure. */
static void removeChild(Node parent, Node node) {
if (isTryFinallyNode(parent, node)) {
if (NodeUtil.hasCatchHandler(getCatchBlock(parent))) {
// A finally can only be removed if there is a catch.
parent.removeChild(node);
} else {
// Otherwise, only its children can be removed.
node.detachChildren();
}
} else if (node.isCatch()) {
// The CATCH can can only be removed if there is a finally clause.
Node tryNode = node.getParent().getParent();
Preconditions.checkState(NodeUtil.hasFinally
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>(tryNode));
node.detachFromParent();
} else if (isTryCatchNodeContainer(node)) {
// The container node itself can't be removed, but the contained CATCH
// can if there is a 'finally' clause
Node tryNode = node.getParent();
Preconditions.checkState(NodeUtil.hasFinally(tryNode));
node.detachChildren();
} else if (node.isBlock()) {
// Simply empty the block. This maintains source location and
// "synthetic"-ness.
node.detachChildren();
} else if (isStatementBlock(parent)
|| isSwitchCase(node)) {
// A statement in a block can simply be removed.
parent.removeChild(node);
} else if (parent.isVar()) {
if (parent.hasMoreThanOneChild()) {
parent.removeChild(node);
} else {
// Remove the node from the parent, so it can be reused.
parent.removeChild(node);
// This would leave an empty VAR, remove the VAR itself.
removeChild(parent.getParent(), parent);
}
} else if (parent.isLabel()
&& node == parent.getLastChild()) {
// Remove the node from the parent, so it can be reused.
parent.removeChild(node);
// A LABEL without children can not be referred to, remove it.
removeChild(parent.getParent(), parent);
} else if (parent.isFor()
&& parent.getChildCount() == 4) {
// Only Token.FOR can have an Token.EMPTY other control structure
// need something for the condition. Others need to be replaced
// or the structure removed.
parent.replaceChild(node, IR.empty());
} else {
throw new IllegalStateException("Invalid attempt to remove node: " +
node.toString() + " of " + parent.toString());
}
}
/**
* Add a finally block if one does not exist.
*/
static void maybeAddFinally(Node tryNode) {
Preconditions.checkState(tryNode.isTry());
if (!NodeUtil.hasFinally(tryNode)) {
tryNode.addChildrenToBack(IR.block().srcref(tryNode));
}
}
/**
* Merge a block with its parent block.
* @return Whether the block was removed.
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>
*/
static boolean tryMergeBlock(Node block) {
Preconditions.checkState(block.isBlock());
Node parent = block.getParent();
// Try to remove the block if its parent is a block/script or if its
// parent is label and it has exactly one child.
if (isStatementBlock(parent)) {
Node previous = block;
while (block.hasChildren()) {
Node child = block.removeFirstChild();
parent.addChildAfter(child, previous);
previous = child;
}
parent.removeChild(block);
return true;
} else {
return false;
}
}
/**
* @param node A node
* @return Whether the call is a NEW or CALL node.
*/
static boolean isCallOrNew(Node node) {
return node.isCall() || node.isNew();
}
/**
* Return a BLOCK node for the given FUNCTION node.
*/
static Node getFunctionBody(Node fn) {
Preconditions.checkArgument(fn.isFunction());
return fn.getLastChild();
}
/**
* Is this node a function declaration? A function declaration is a function
* that has a name that is added to the current scope (i.e. a function that
* is not part of a expression; see {@link #isFunctionExpression}).
*/
static boolean isFunctionDeclaration(Node n) {
return n.isFunction() && isStatement(n);
}
/**
* Is this node a hoisted function declaration? A function declaration in the
* scope root is hoisted to the top of the scope.
* See {@link #isFunctionDeclaration}).
*/
static boolean isHoistedFunctionDeclaration(Node n) {
return isFunctionDeclaration(n)
&& (n.getParent().isScript()
|| n.getParent().getParent().isFunction());
}
/**
* Is a FUNCTION node an function expression? An function expression is one
* that has either no name or a name that is not added to the current scope.
*
* <p>Some examples of function expressions:
* <pre>
* (function () {})
* (function f() {})()
* [ function f() {} ]
* var f = function f() {};
* for (function f() {};;) {}
* </pre>
*
* <p>Some
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> examples of functions that are <em>not</em> expressions:
* <pre>
* function f() {}
* if (x); else function f() {}
* for (;;) { function f() {} }
* </pre>
*
* @param n A node
* @return Whether n is an function used within an expression.
*/
static boolean isFunctionExpression(Node n) {
return n.isFunction() && !isStatement(n);
}
/**
* Returns whether this is a bleeding function (an anonymous named function
* that bleeds into the inner scope).
*/
static boolean isBleedingFunctionName(Node n) {
return n.isName() && !n.getString().isEmpty() &&
isFunctionExpression(n.getParent());
}
/**
* Determines if a node is a function expression that has an empty body.
*
* @param node a node
* @return whether the given node is a function expression that is empty
*/
static boolean isEmptyFunctionExpression(Node node) {
return isFunctionExpression(node) && isEmptyBlock(node.getLastChild());
}
/**
* Determines if a function takes a variable number of arguments by
* looking for references to the "arguments" var_args object.
*/
static boolean isVarArgsFunction(Node function) {
// TODO(johnlenz): rename this function
Preconditions.checkArgument(function.isFunction());
return isNameReferenced(
function.getLastChild(),
"arguments",
MATCH_NOT_FUNCTION);
}
/**
* @return Whether node is a call to methodName.
* a.f(...)
* a['f'](...)
*/
static boolean isObjectCallMethod(Node callNode, String methodName) {
if (callNode.isCall()) {
Node functionIndentifyingExpression = callNode.getFirstChild();
if (isGet(functionIndentifyingExpression)) {
Node last = functionIndentifyingExpression.getLastChild();
if (last != null && last.isString()) {
String propName = last.getString();
return (propName.equals(methodName));
}
}
}
return false;
}
/**
* @return Whether the callNode represents an expression in the form of:
* x.call(...)
* x['call'](...)
*/
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>
static boolean isFunctionObjectCall(Node callNode) {
return isObjectCallMethod(callNode, "call");
}
/**
* @return Whether the callNode represents an expression in the form of:
* x.apply(...)
* x['apply'](...)
*/
static boolean isFunctionObjectApply(Node callNode) {
return isObjectCallMethod(callNode, "apply");
}
/**
* Determines whether this node is strictly on the left hand side of an assign
* or var initialization. Notably, this does not include all L-values, only
* statements where the node is used only as an L-value.
*
* @param n The node
* @param parent Parent of the node
* @return True if n is the left hand of an assign
*/
static boolean isVarOrSimpleAssignLhs(Node n, Node parent) {
return (parent.isAssign() && parent.getFirstChild() == n) ||
parent.isVar();
}
/**
* Determines whether this node is used as an L-value. Notice that sometimes
* names are used as both L-values and R-values.
*
* We treat "var x;" as a pseudo-L-value, which kind of makes sense if you
* treat it as "assignment to 'undefined' at the top of the scope". But if
* we're honest with ourselves, it doesn't make sense, and we only do this
* because it makes sense to treat this as syntactically similar to
* "var x = 0;".
*
* @param n The node
* @return True if n is an L-value.
*/
public static boolean isLValue(Node n) {
Preconditions.checkArgument(n.isName() || n.isGetProp() ||
n.isGetElem());
Node parent = n.getParent();
if (parent == null) {
return false;
}
return (NodeUtil.isAssignmentOp(parent) && parent.getFirstChild() == n)
|| (NodeUtil.isForIn(parent) && parent.getFirstChild() == n)
|| parent.isVar()
|| (parent.isFunction() && parent.getFirstChild() == n)
|| parent.isDec()
|| parent.isInc()
||
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> parent.isParamList()
|| parent.isCatch();
}
/**
* Determines whether a node represents an object literal key
* (e.g. key1 in {key1: value1, key2: value2}).
*
* @param node A node
*/
static boolean isObjectLitKey(Node node) {
switch (node.getType()) {
case Token.STRING_KEY:
case Token.GETTER_DEF:
case Token.SETTER_DEF:
return true;
}
return false;
}
/**
* Get the name of an object literal key.
*
* @param key A node
*/
static String getObjectLitKeyName(Node key) {
switch (key.getType()) {
case Token.STRING_KEY:
case Token.GETTER_DEF:
case Token.SETTER_DEF:
return key.getString();
}
throw new IllegalStateException("Unexpected node type: " + key);
}
/**
* @param key A OBJECTLIT key node.
* @return The type expected when using the key.
*/
static JSType getObjectLitKeyTypeFromValueType(Node key, JSType valueType) {
if (valueType != null) {
switch (key.getType()) {
case Token.GETTER_DEF:
// GET must always return a function type.
if (valueType.isFunctionType()) {
FunctionType fntype = valueType.toMaybeFunctionType();
valueType = fntype.getReturnType();
} else {
return null;
}
break;
case Token.SETTER_DEF:
if (valueType.isFunctionType()) {
// SET must always return a function type.
FunctionType fntype = valueType.toMaybeFunctionType();
Node param = fntype.getParametersNode().getFirstChild();
// SET function must always have one parameter.
valueType = param.getJSType();
} else {
return null;
}
break;
}
}
return valueType;
}
/**
* Determines whether a node represents an object literal get or set key
* (e.g. key1 in {get key1() {}, set key2(a){}).
*
* @param node A node
*/
static boolean isGetOrSetKey(Node node) {
switch (node.getType
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>()) {
case Token.GETTER_DEF:
case Token.SETTER_DEF:
return true;
}
return false;
}
/**
* Converts an operator's token value (see {@link Token}) to a string
* representation.
*
* @param operator the operator's token value to convert
* @return the string representation or {@code null} if the token value is
* not an operator
*/
static String opToStr(int operator) {
switch (operator) {
case Token.BITOR: return "|";
case Token.OR: return "||";
case Token.BITXOR: return "^";
case Token.AND: return "&&";
case Token.BITAND: return "&";
case Token.SHEQ: return "===";
case Token.EQ: return "==";
case Token.NOT: return "!";
case Token.NE: return "!=";
case Token.SHNE: return "!==";
case Token.LSH: return "<<";
case Token.IN: return "in";
case Token.LE: return "<=";
case Token.LT: return "<";
case Token.URSH: return ">>>";
case Token.RSH: return ">>";
case Token.GE: return ">=";
case Token.GT: return ">";
case Token.MUL: return "*";
case Token.DIV: return "/";
case Token.MOD: return "%";
case Token.BITNOT: return "~";
case Token.ADD: return "+";
case Token.SUB: return "-";
case Token.POS: return "+";
case Token.NEG: return "-";
case Token.ASSIGN: return "=";
case Token.ASSIGN_BITOR: return "|=";
case Token.ASSIGN_BITXOR: return "^=";
case Token.ASSIGN_BITAND: return "&=";
case Token.ASSIGN_LSH: return "<<=";
case Token.ASSIGN_RSH: return ">>=";
case Token.ASSIGN_URSH: return ">>>=";
case Token.ASSIGN_ADD: return "+=";
case Token.ASSIGN_SUB: return "-=";
case Token.ASSIGN_MUL: return "*=";
case Token.ASSIGN_DIV: return "/=";
case Token.ASSIGN_
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>MOD: return "%=";
case Token.VOID: return "void";
case Token.TYPEOF: return "typeof";
case Token.INSTANCEOF: return "instanceof";
default: return null;
}
}
/**
* Converts an operator's token value (see {@link Token}) to a string
* representation or fails.
*
* @param operator the operator's token value to convert
* @return the string representation
* @throws Error if the token value is not an operator
*/
static String opToStrNoFail(int operator) {
String res = opToStr(operator);
if (res == null) {
throw new Error("Unknown op " + operator + ": " +
Token.name(operator));
}
return res;
}
/**
* @return true if n or any of its children are of the specified type
*/
static boolean containsType(Node node,
int type,
Predicate<Node> traverseChildrenPred) {
return has(node, new MatchNodeType(type), traverseChildrenPred);
}
/**
* @return true if n or any of its children are of the specified type
*/
static boolean containsType(Node node, int type) {
return containsType(node, type, Predicates.<Node>alwaysTrue());
}
/**
* Given a node tree, finds all the VAR declarations in that tree that are
* not in an inner scope. Then adds a new VAR node at the top of the current
* scope that redeclares them, if necessary.
*/
static void redeclareVarsInsideBranch(Node branch) {
Collection<Node> vars = getVarsDeclaredInBranch(branch);
if (vars.isEmpty()) {
return;
}
Node parent = getAddingRoot(branch);
for (Node nameNode : vars) {
Node var = IR.var(
IR.name(nameNode.getString())
.srcref(nameNode))
.srcref(nameNode);
copyNameAnnotations(nameNode, var.getFirstChild());
parent.addChildToFront(var);
}
}
/**
* Copy any annotations that follow a named value.
* @param source
* @param destination
*/
static void copyNameAnnotations(Node source, Node destination) {
if (source.getBooleanProp(Node.IS
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>_CONSTANT_NAME)) {
destination.putBooleanProp(Node.IS_CONSTANT_NAME, true);
}
}
/**
* Gets a Node at the top of the current scope where we can add new var
* declarations as children.
*/
private static Node getAddingRoot(Node n) {
Node addingRoot = null;
Node ancestor = n;
while (null != (ancestor = ancestor.getParent())) {
int type = ancestor.getType();
if (type == Token.SCRIPT) {
addingRoot = ancestor;
break;
} else if (type == Token.FUNCTION) {
addingRoot = ancestor.getLastChild();
break;
}
}
// make sure that the adding root looks ok
Preconditions.checkState(addingRoot.isBlock() ||
addingRoot.isScript());
Preconditions.checkState(addingRoot.getFirstChild() == null ||
!addingRoot.getFirstChild().isScript());
return addingRoot;
}
/**
* Creates a node representing a qualified name.
*
* @param name A qualified name (e.g. "foo" or "foo.bar.baz")
* @return A NAME or GETPROP node
*/
public static Node newQualifiedNameNode(
CodingConvention convention, String name) {
int endPos = name.indexOf('.');
if (endPos == -1) {
return newName(convention, name);
}
Node node;
String nodeName = name.substring(0, endPos);
if ("this".equals(nodeName)) {
node = IR.thisNode();
} else {
node = newName(convention, nodeName);
}
int startPos;
do {
startPos = endPos + 1;
endPos = name.indexOf('.', startPos);
String part = (endPos == -1
? name.substring(startPos)
: name.substring(startPos, endPos));
Node propNode = IR.string(part);
if (convention.isConstantKey(part)) {
propNode.putBooleanProp(Node.IS_CONSTANT_NAME, true);
}
node = IR.getprop(node, propNode);
} while (endPos != -1);
return node;
}
/**
* Creates a node representing a qualified name.
*
* @param name A
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> string to be checked for ASCII-goodness.
*
* @return True if all characters in the string are in Basic Latin set.
*/
static boolean isLatin(String s) {
int len = s.length();
for (int index = 0; index < len; index++) {
char c = s.charAt(index);
if (c > LARGEST_BASIC_LATIN) {
return false;
}
}
return true;
}
/**
* Determines whether the given name is a valid variable name.
*/
static boolean isValidSimpleName(String name) {
return TokenStream.isJSIdentifier(name) &&
!TokenStream.isKeyword(name) &&
// no Unicode escaped characters - some browsers are less tolerant
// of Unicode characters that might be valid according to the
// language spec.
// Note that by this point, Unicode escapes have been converted
// to UTF-16 characters, so we're only searching for character
// values, not escapes.
isLatin(name);
}
/**
* Determines whether the given name is a valid qualified name.
*/
// TODO(nicksantos): This should be moved into a "Language" API,
// so that the results are different for es5 and es3.
public static boolean isValidQualifiedName(String name) {
if (name.endsWith(".") || name.startsWith(".")) {
return false;
}
String[] parts = name.split("\\.");
for (String part : parts) {
if (!isValidSimpleName(part)) {
return false;
}
}
return true;
}
/**
* Determines whether the given name can appear on the right side of
* the dot operator. Many properties (like reserved words) cannot.
*/
static boolean isValidPropertyName(String name) {
return isValidSimpleName(name);
}
private static class VarCollector implements Visitor {
final Map<String, Node> vars = Maps.newLinkedHashMap();
@Override
public void visit(Node n) {
if (n.isName()) {
Node parent = n.getParent();
if (parent != null && parent.isVar()) {
String name = n.getString();
if (!vars.containsKey(name)) {
vars.put(name, n);
}
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> Preconditions.checkState(value.getNext() == null);
nodeName.addChildToBack(value);
nodeName.srcref(value);
}
Node var = IR.var(nodeName).srcref(nodeName);
return var;
}
/**
* A predicate for matching name nodes with the specified node.
*/
private static class MatchNameNode implements Predicate<Node>{
final String name;
MatchNameNode(String name){
this.name = name;
}
@Override
public boolean apply(Node n) {
return n.isName() && n.getString().equals(name);
}
}
/**
* A predicate for matching nodes with the specified type.
*/
static class MatchNodeType implements Predicate<Node>{
final int type;
MatchNodeType(int type){
this.type = type;
}
@Override
public boolean apply(Node n) {
return n.getType() == type;
}
}
/**
* A predicate for matching var or function declarations.
*/
static class MatchDeclaration implements Predicate<Node> {
@Override
public boolean apply(Node n) {
return isFunctionDeclaration(n) || n.isVar();
}
}
/**
* A predicate for matching anything except function nodes.
*/
private static class MatchNotFunction implements Predicate<Node>{
@Override
public boolean apply(Node n) {
return !n.isFunction();
}
}
static final Predicate<Node> MATCH_NOT_FUNCTION = new MatchNotFunction();
/**
* A predicate for matching statements without exiting the current scope.
*/
static class MatchShallowStatement implements Predicate<Node>{
@Override
public boolean apply(Node n) {
Node parent = n.getParent();
return n.isBlock()
|| (!n.isFunction() && (parent == null
|| isControlStructure(parent)
|| isStatementBlock(parent)));
}
}
/**
* Finds the number of times a type is referenced within the node tree.
*/
static int getNodeTypeReferenceCount(
Node node, int type, Predicate<Node> traverseChildrenPred) {
return getCount(node, new MatchNodeType(type), traverseChildrenPred);
}
/**
* Whether a simple name is referenced within the node tree.
*/
static boolean isNameReferenced(Node node,
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> {
for (Node c = node.getFirstChild(); c != null; c = c.getNext()) {
visitPreOrder(c, visitor, traverseChildrenPred);
}
}
}
/**
* A post-order traversal, calling Visitor.visit for each child matching
* the predicate.
*/
static void visitPostOrder(Node node,
Visitor visitor,
Predicate<Node> traverseChildrenPred) {
if (traverseChildrenPred.apply(node)) {
for (Node c = node.getFirstChild(); c != null; c = c.getNext()) {
visitPostOrder(c, visitor, traverseChildrenPred);
}
}
visitor.visit(node);
}
/**
* @return Whether a TRY node has a finally block.
*/
static boolean hasFinally(Node n) {
Preconditions.checkArgument(n.isTry());
return n.getChildCount() == 3;
}
/**
* @return The BLOCK node containing the CATCH node (if any)
* of a TRY.
*/
static Node getCatchBlock(Node n) {
Preconditions.checkArgument(n.isTry());
return n.getFirstChild().getNext();
}
/**
* @return Whether BLOCK (from a TRY node) contains a CATCH.
* @see NodeUtil#getCatchBlock
*/
static boolean hasCatchHandler(Node n) {
Preconditions.checkArgument(n.isBlock());
return n.hasChildren() && n.getFirstChild().isCatch();
}
/**
* @param fnNode The function.
* @return The Node containing the Function parameters.
*/
public static Node getFunctionParameters(Node fnNode) {
// Function NODE: [ FUNCTION -> NAME, LP -> ARG1, ARG2, ... ]
Preconditions.checkArgument(fnNode.isFunction());
return fnNode.getFirstChild().getNext();
}
/**
* <p>Determines whether a variable is constant:
* <ol>
* <li>In Normalize, any name that matches the
* {@link CodingConvention#isConstant(String)} is annotated with an
* IS_CONSTANT_NAME property.
* </ol>
*
* @param node A NAME or STRING node
* @return True if a name node represents a constant variable
*/
static boolean is
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>ConstantName(Node node) {
return node.getBooleanProp(Node.IS_CONSTANT_NAME);
}
/** Whether the given name is constant by coding convention. */
static boolean isConstantByConvention(
CodingConvention convention, Node node, Node parent) {
if (parent.isGetProp() && node == parent.getLastChild()) {
return convention.isConstantKey(node.getString());
} else if (isObjectLitKey(node)) {
return convention.isConstantKey(node.getString());
} else if (node.isName()) {
return convention.isConstant(node.getString());
}
return false;
}
/**
* Get the JSDocInfo for a function.
*/
public static JSDocInfo getFunctionJSDocInfo(Node n) {
Preconditions.checkState(n.isFunction());
JSDocInfo fnInfo = n.getJSDocInfo();
if (fnInfo == null && NodeUtil.isFunctionExpression(n)) {
// Look for the info on other nodes.
Node parent = n.getParent();
if (parent.isAssign()) {
// on ASSIGNs
fnInfo = parent.getJSDocInfo();
} else if (parent.isName()) {
// on var NAME = function() { ... };
fnInfo = parent.getParent().getJSDocInfo();
}
}
return fnInfo;
}
/**
* @param n The node.
* @return The source name property on the node or its ancestors.
*/
public static String getSourceName(Node n) {
String sourceName = null;
while (sourceName == null && n != null) {
sourceName = n.getSourceFileName();
n = n.getParent();
}
return sourceName;
}
/**
* @param n The node.
* @return The source name property on the node or its ancestors.
*/
public static StaticSourceFile getSourceFile(Node n) {
StaticSourceFile sourceName = null;
while (sourceName == null && n != null) {
sourceName = n.getStaticSourceFile();
n = n.getParent();
}
return sourceName;
}
/**
* @param n The node.
* @return The InputId property on the node or its ancestors.
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> */
public static InputId getInputId(Node n) {
while (n != null && !n.isScript()) {
n = n.getParent();
}
return (n != null && n.isScript()) ? n.getInputId() : null;
}
/**
* A new CALL node with the "FREE_CALL" set based on call target.
*/
static Node newCallNode(Node callTarget, Node... parameters) {
boolean isFreeCall = !isGet(callTarget);
Node call = IR.call(callTarget);
call.putBooleanProp(Node.FREE_CALL, isFreeCall);
for (Node parameter : parameters) {
call.addChildToBack(parameter);
}
return call;
}
/**
* @return Whether the node is known to be a value that is not referenced
* elsewhere.
*/
static boolean evaluatesToLocalValue(Node value) {
return evaluatesToLocalValue(value, Predicates.<Node>alwaysFalse());
}
/**
* @param locals A predicate to apply to unknown local values.
* @return Whether the node is known to be a value that is not a reference
* outside the expression scope.
*/
static boolean evaluatesToLocalValue(Node value, Predicate<Node> locals) {
switch (value.getType()) {
case Token.CAST:
return evaluatesToLocalValue(value.getFirstChild(), locals);
case Token.ASSIGN:
// A result that is aliased by a non-local name, is the effectively the
// same as returning a non-local name, but this doesn't matter if the
// value is immutable.
return NodeUtil.isImmutableValue(value.getLastChild())
|| (locals.apply(value)
&& evaluatesToLocalValue(value.getLastChild(), locals));
case Token.COMMA:
return evaluatesToLocalValue(value.getLastChild(), locals);
case Token.AND:
case Token.OR:
return evaluatesToLocalValue(value.getFirstChild(), locals)
&& evaluatesToLocalValue(value.getLastChild(), locals);
case Token.HOOK:
return evaluatesToLocalValue(value.getFirstChild().getNext(), locals)
&& evaluatesToLocalValue(value.getLastChild(), locals);
case Token.INC:
case Token.DEC:
if (value.getBooleanProp(Node.INCRDE
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>CR_PROP)) {
return evaluatesToLocalValue(value.getFirstChild(), locals);
} else {
return true;
}
case Token.THIS:
return locals.apply(value);
case Token.NAME:
return isImmutableValue(value) || locals.apply(value);
case Token.GETELEM:
case Token.GETPROP:
// There is no information about the locality of object properties.
return locals.apply(value);
case Token.CALL:
return callHasLocalResult(value)
|| isToStringMethodCall(value)
|| locals.apply(value);
case Token.NEW:
return newHasLocalResult(value)
|| locals.apply(value);
case Token.FUNCTION:
case Token.REGEXP:
case Token.ARRAYLIT:
case Token.OBJECTLIT:
// Literals objects with non-literal children are allowed.
return true;
case Token.DELPROP:
case Token.IN:
// TODO(johnlenz): should IN operator be included in #isSimpleOperator?
return true;
default:
// Other op force a local value:
// x = '' + g (x is now an local string)
// x -= g (x is now an local number)
if (isAssignmentOp(value)
|| isSimpleOperator(value)
|| isImmutableValue(value)) {
return true;
}
throw new IllegalStateException(
"Unexpected expression node" + value +
"\n parent:" + value.getParent());
}
}
/**
* Given the first sibling, this returns the nth
* sibling or null if no such sibling exists.
* This is like "getChildAtIndex" but returns null for non-existent indexes.
*/
private static Node getNthSibling(Node first, int index) {
Node sibling = first;
while (index != 0 && sibling != null) {
sibling = sibling.getNext();
index--;
}
return sibling;
}
/**
* Given the function, this returns the nth
* argument or null if no such parameter exists.
*/
static Node getArgumentForFunction(Node function, int index) {
Preconditions.checkState(function.isFunction());
return getNthSibling(
function.getFirstChild().getNext().getFirstChild(), index);
}
/**
* Given
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> the new or call, this returns the nth
* argument of the call or null if no such argument exists.
*/
static Node getArgumentForCallOrNew(Node call, int index) {
Preconditions.checkState(isCallOrNew(call));
return getNthSibling(
call.getFirstChild().getNext(), index);
}
/**
* Returns whether this is a target of a call or new.
*/
static boolean isCallOrNewTarget(Node target) {
Node parent = target.getParent();
return parent != null
&& NodeUtil.isCallOrNew(parent)
&& parent.getFirstChild() == target;
}
private static boolean isToStringMethodCall(Node call) {
Node getNode = call.getFirstChild();
if (isGet(getNode)) {
Node propNode = getNode.getLastChild();
return propNode.isString() && "toString".equals(propNode.getString());
}
return false;
}
/** Find the best JSDoc for the given node. */
static JSDocInfo getBestJSDocInfo(Node n) {
JSDocInfo info = n.getJSDocInfo();
if (info == null) {
Node parent = n.getParent();
if (parent == null) {
return null;
}
if (parent.isName()) {
return getBestJSDocInfo(parent);
} else if (parent.isAssign()) {
return getBestJSDocInfo(parent);
} else if (isObjectLitKey(parent)) {
return parent.getJSDocInfo();
} else if (parent.isFunction()) {
return parent.getJSDocInfo();
} else if (parent.isVar() && parent.hasOneChild()) {
return parent.getJSDocInfo();
} else if ((parent.isHook() && parent.getFirstChild() != n) ||
parent.isOr() ||
parent.isAnd() ||
(parent.isComma() && parent.getFirstChild() != n)) {
return getBestJSDocInfo(parent);
} else if (parent.isCast()) {
return parent.getJSDocInfo();
}
}
return info;
}
/** Find the l-value that the given r-value is being assigned to. */
static
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> Node getBestLValue(Node n) {
Node parent = n.getParent();
boolean isFunctionDeclaration = isFunctionDeclaration(n);
if (isFunctionDeclaration) {
return n.getFirstChild();
} else if (parent.isName()) {
return parent;
} else if (parent.isAssign()) {
return parent.getFirstChild();
} else if (isObjectLitKey(parent)) {
return parent;
} else if (
(parent.isHook() && parent.getFirstChild() != n) ||
parent.isOr() ||
parent.isAnd() ||
(parent.isComma() && parent.getFirstChild() != n)) {
return getBestLValue(parent);
} else if (parent.isCast()) {
return getBestLValue(parent);
}
return null;
}
/** Gets the r-value of a node returned by getBestLValue. */
static Node getRValueOfLValue(Node n) {
Node parent = n.getParent();
switch (parent.getType()) {
case Token.ASSIGN:
return n.getNext();
case Token.VAR:
return n.getFirstChild();
case Token.FUNCTION:
return parent;
}
return null;
}
/** Get the owner of the given l-value node. */
static Node getBestLValueOwner(@Nullable Node lValue) {
if (lValue == null || lValue.getParent() == null) {
return null;
}
if (isObjectLitKey(lValue)) {
return getBestLValue(lValue.getParent());
} else if (isGet(lValue)) {
return lValue.getFirstChild();
}
return null;
}
/** Get the name of the given l-value node. */
static String getBestLValueName(@Nullable Node lValue) {
if (lValue == null || lValue.getParent() == null) {
return null;
}
if (isObjectLitKey(lValue)) {
Node owner = getBestLValue(lValue.getParent());
if (owner != null) {
String ownerName = getBestLValueName(owner);
if (ownerName != null) {
return ownerName + "." + getObjectLitKeyName(lValue);
}
}
return null;
}
return
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> lValue.getQualifiedName();
}
/**
* @returns false iff the result of the expression is not consumed.
*/
static boolean isExpressionResultUsed(Node expr) {
// TODO(johnlenz): consider sharing some code with trySimpleUnusedResult.
Node parent = expr.getParent();
switch (parent.getType()) {
case Token.BLOCK:
case Token.EXPR_RESULT:
return false;
case Token.CAST:
return isExpressionResultUsed(parent);
case Token.HOOK:
case Token.AND:
case Token.OR:
return (expr == parent.getFirstChild())
? true : isExpressionResultUsed(parent);
case Token.COMMA:
Node gramps = parent.getParent();
if (gramps.isCall() &&
parent == gramps.getFirstChild()) {
// Semantically, a direct call to eval is different from an indirect
// call to an eval. See ECMA-262 S15.1.2.1. So it's OK for the first
// expression to a comma to be a no-op if it's used to indirect
// an eval. This we pretend that this is "used".
if (expr == parent.getFirstChild() &&
parent.getChildCount() == 2 &&
expr.getNext().isName() &&
"eval".equals(expr.getNext().getString())) {
return true;
}
}
return (expr == parent.getFirstChild())
? false : isExpressionResultUsed(parent);
case Token.FOR:
if (!NodeUtil.isForIn(parent)) {
// Only an expression whose result is in the condition part of the
// expression is used.
return (parent.getChildAtIndex(1) == expr);
}
break;
}
return true;
}
/**
* @param n The expression to check.
* @return Whether the expression is unconditionally executed only once in the
* containing execution scope.
*/
static boolean isExecutedExactlyOnce(Node n) {
inspect: do {
Node parent = n.getParent();
switch (parent.getType()) {
case Token.IF:
case Token.HOOK:
case Token.AND:
case Token.OR:
if (parent.getFirstChild() != n) {
return false;
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>
}
// other ancestors may be conditional
continue inspect;
case Token.FOR:
if (NodeUtil.isForIn(parent)) {
if (parent.getChildAtIndex(1) != n) {
return false;
}
} else {
if (parent.getFirstChild() != n) {
return false;
}
}
// other ancestors may be conditional
continue inspect;
case Token.WHILE:
case Token.DO:
return false;
case Token.TRY:
// Consider all code under a try/catch to be conditionally executed.
if (!hasFinally(parent) || parent.getLastChild() != n) {
return false;
}
continue inspect;
case Token.CASE:
case Token.DEFAULT_CASE:
return false;
case Token.SCRIPT:
case Token.FUNCTION:
// Done, we've reached the scope root.
break inspect;
}
} while ((n = n.getParent()) != null);
return true;
}
/**
* @return An appropriate AST node for the boolean value.
*/
static Node booleanNode(boolean value) {
return value ? IR.trueNode() : IR.falseNode();
}
/**
* @return An appropriate AST node for the double value.
*/
static Node numberNode(double value, Node srcref) {
Node result;
if (Double.isNaN(value)) {
result = IR.name("NaN");
} else if (value == Double.POSITIVE_INFINITY) {
result = IR.name("Infinity");
} else if (value == Double.NEGATIVE_INFINITY) {
result = IR.neg(IR.name("Infinity"));
} else {
result = IR.number(value);
}
if (srcref != null) {
result.srcrefTree(srcref);
}
return result;
}
static boolean isNaN(Node n) {
if ((n.isName() && n.getString().equals("NaN")) ||
(n.getType() == Token.DIV &&
n.getFirstChild().isNumber() && n.getFirstChild().getDouble() == 0 &&
n.getLastChild().isNumber() && n.getLastChild().getDouble() == 0)) {
return true;
}
return false;
}
/**
* Given
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> maps m1
* and m2 is the map of the union of names with {@link JSType#getLeastSupertype}
* to meet the m1 type and m2 type.
*
* @see NodeTraversal
* @see DataFlowAnalysis
*
*/
public class Scope
implements StaticScope<JSType>, StaticSymbolTable<Scope.Var, Scope.Var> {
private final Map<String, Var> vars = new LinkedHashMap<String, Var>();
private final Scope parent;
private final int depth;
private final Node rootNode;
/** Whether this is a bottom scope for the purposes of type inference. */
private final boolean isBottom;
private Var arguments;
private static final Predicate<Var> DECLARATIVELY_UNBOUND_VARS_WITHOUT_TYPES =
new Predicate<Var>() {
@Override public boolean apply(Var var) {
return var.getParentNode() != null &&
var.getType() == null && // no declared type
var.getParentNode().isVar() &&
!var.isExtern();
}
};
/** Stores info about a variable */
public static class Var
implements StaticSlot<JSType>, StaticReference<JSType> {
/** name */
final String name;
/** Var node */
final Node nameNode;
/**
* The variable's type.
*/
private JSType type;
/**
* Whether the variable's type has been inferred or is declared. An inferred
* type may change over time (as more code is discovered), whereas a
* declared type is a static contract that must be matched.
*/
private final boolean typeInferred;
/** Input source */
final CompilerInput input;
/**
* The index at which the var is declared. e..g if it's 0, it's the first
* declared variable in that scope
*/
final int index;
/** The enclosing scope */
final Scope scope;
/** @see #isMarkedEscaped */
private boolean markedEscaped = false;
/** @see #isMarkedAssignedExactlyOnce */
private boolean markedAssignedExactlyOnce = false;
/**
* Creates a variable.
*
* @param inferred whether its type is inferred (as opposed to declared)
*/
private Var(boolean inferred, String name, Node nameNode, JSType type,
Scope scope,
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> int index, CompilerInput input) {
this.name = name;
this.nameNode = nameNode;
this.type = type;
this.scope = scope;
this.index = index;
this.input = input;
this.typeInferred = inferred;
}
/**
* Gets the name of the variable.
*/
@Override
public String getName() {
return name;
}
/**
* Gets the node for the name of the variable.
*/
@Override
public Node getNode() {
return nameNode;
}
CompilerInput getInput() {
return input;
}
@Override
public StaticSourceFile getSourceFile() {
return nameNode.getStaticSourceFile();
}
@Override
public Var getSymbol() {
return this;
}
@Override
public Var getDeclaration() {
return nameNode == null ? null : this;
}
/**
* Gets the parent of the name node.
*/
public Node getParentNode() {
return nameNode == null ? null : nameNode.getParent();
}
/**
* Whether this is a bleeding function (an anonymous named function
* that bleeds into the inner scope).
*/
public boolean isBleedingFunction() {
return NodeUtil.isFunctionExpression(getParentNode());
}
/**
* Gets the scope where this variable is declared.
*/
Scope getScope() {
return scope;
}
/**
* Returns whether this is a global variable.
*/
public boolean isGlobal() {
return scope.isGlobal();
}
/**
* Returns whether this is a local variable.
*/
public boolean isLocal() {
return scope.isLocal();
}
/**
* Returns whether this is defined in an extern file.
*/
boolean isExtern() {
return input == null || input.isExtern();
}
/**
* Returns {@code true} if the variable is declared as a constant,
* based on the value reported by {@code NodeUtil}.
*/
public boolean isConst() {
return nameNode != null && NodeUtil.isConstantName(nameNode);
}
/**
* Returns {@code true} if the variable is declared as a define.
* A variable is a define if it is annotated by {@code @define}.
*/
public
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> by an inner scope.
*
* In other words, it's assigned in an inner scope so that it's much harder
* to make assertions about its value at a given point.
*/
void markEscaped() {
markedEscaped = true;
}
/**
* Whether this is escaped by an inner scope.
* Notice that not all scope creators record this information.
*/
boolean isMarkedEscaped() {
return markedEscaped;
}
/**
* Record that this is assigned exactly once..
*
* In other words, it's assigned in an inner scope so that it's much harder
* to make assertions about its value at a given point.
*/
void markAssignedExactlyOnce() {
markedAssignedExactlyOnce = true;
}
/**
* Whether this is assigned exactly once.
* Notice that not all scope creators record this information.
*/
boolean isMarkedAssignedExactlyOnce() {
return markedAssignedExactlyOnce;
}
}
/**
* A special subclass of Var used to distinguish "arguments" in the current
* scope.
*/
// TODO(johnlenz): Include this the list of Vars for the scope.
public static class Arguments extends Var {
Arguments(Scope scope) {
super(
false, // no inferred
"arguments", // always arguments
null, // no declaration node
// TODO(johnlenz): provide the type of "Arguments".
null, // no type info
scope,
-1, // no variable index
null // input
);
}
@Override public boolean equals(Object other) {
if (!(other instanceof Arguments)) {
return false;
}
Arguments otherVar = (Arguments) other;
return otherVar.scope.getRootNode() == scope.getRootNode();
}
@Override public int hashCode() {
return System.identityHashCode(this);
}
}
/**
* Creates a Scope given the parent Scope and the root node of the scope.
* @param parent The parent Scope. Cannot be null.
* @param rootNode Typically the FUNCTION node.
*/
Scope(Scope parent, Node rootNode) {
Preconditions.checkNotNull(parent);
Preconditions.checkArgument(rootNode != parent.rootNode);
this.parent = parent;
this.rootNode = rootNode;
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> this.isBottom = false;
this.depth = parent.depth + 1;
}
/**
* Creates a empty Scope (bottom of the lattice).
* @param rootNode Typically a FUNCTION node or the global BLOCK node.
* @param isBottom Whether this is the bottom of a lattice. Otherwise,
* it must be a global scope.
*/
private Scope(Node rootNode, boolean isBottom) {
this.parent = null;
this.rootNode = rootNode;
this.isBottom = isBottom;
this.depth = 0;
}
static Scope createGlobalScope(Node rootNode) {
return new Scope(rootNode, false);
}
static Scope createLatticeBottom(Node rootNode) {
return new Scope(rootNode, true);
}
/** The depth of the scope. The global scope has depth 0. */
int getDepth() {
return depth;
}
/** Whether this is the bottom of the lattice. */
boolean isBottom() {
return isBottom;
}
/**
* Gets the container node of the scope. This is typically the FUNCTION
* node or the global BLOCK/SCRIPT node.
*/
@Override
public Node getRootNode() {
return rootNode;
}
public Scope getParent() {
return parent;
}
Scope getGlobalScope() {
Scope result = this;
while (result.getParent() != null) {
result = result.getParent();
}
return result;
}
@Override
public StaticScope<JSType> getParentScope() {
return parent;
}
/**
* Gets the type of {@code this} in the current scope.
*/
@Override
public JSType getTypeOfThis() {
if (isGlobal()) {
return ObjectType.cast(rootNode.getJSType());
}
Preconditions.checkState(rootNode.isFunction());
JSType nodeType = rootNode.getJSType();
if (nodeType != null && nodeType.isFunctionType()) {
return nodeType.toMaybeFunctionType().getTypeOfThis();
} else {
return parent.getTypeOfThis();
}
}
/**
* Declares a variable whose type is inferred.
*
* @param name name of the variable
* @param nameNode the NAME node declaring the variable
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> * @param type the variable's type
* @param input the input in which this variable is defined.
*/
Var declare(String name, Node nameNode, JSType type, CompilerInput input) {
return declare(name, nameNode, type, input, true);
}
/**
* Declares a variable.
*
* @param name name of the variable
* @param nameNode the NAME node declaring the variable
* @param type the variable's type
* @param input the input in which this variable is defined.
* @param inferred Whether this variable's type is inferred (as opposed
* to declared).
*/
Var declare(String name, Node nameNode,
JSType type, CompilerInput input, boolean inferred) {
Preconditions.checkState(name != null && name.length() > 0);
// Make sure that it's declared only once
Preconditions.checkState(vars.get(name) == null);
Var var = new Var(inferred, name, nameNode, type, this, vars.size(), input);
vars.put(name, var);
return var;
}
/**
* Undeclares a variable, to be used when the compiler optimizes out
* a variable and removes it from the scope.
*/
void undeclare(Var var) {
Preconditions.checkState(var.scope == this);
Preconditions.checkState(vars.get(var.name) == var);
vars.remove(var.name);
}
@Override
public Var getSlot(String name) {
return getVar(name);
}
@Override
public Var getOwnSlot(String name) {
return vars.get(name);
}
/**
* Returns the variable, may be null
*/
public Var getVar(String name) {
Var var = vars.get(name);
if (var != null) {
return var;
} else if (parent != null) { // Recurse up the parent Scope
return parent.getVar(name);
} else {
return null;
}
}
/**
* Get a unique VAR object to represents "arguments" within this scope
*/
public Var getArgumentsVar() {
if (arguments == null) {
arguments = new Arguments(this);
}
return arguments;
}
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> /**
* Returns true if a variable is declared.
*/
public boolean isDeclared(String name, boolean recurse) {
Scope scope = this;
if (scope.vars.containsKey(name)) {
return true;
}
if (scope.parent != null && recurse) {
return scope.parent.isDeclared(name, recurse);
}
return false;
}
/**
* Return an iterator over all of the variables declared in this scope.
*/
public Iterator<Var> getVars() {
return vars.values().iterator();
}
/**
* Return an iterable over all of the variables declared in this scope.
*/
Iterable<Var> getVarIterable() {
return vars.values();
}
@Override
public Iterable<Var> getReferences(Var var) {
return ImmutableList.of(var);
}
@Override
public StaticScope<JSType> getScope(Var var) {
return var.scope;
}
@Override
public Iterable<Var> getAllSymbols() {
return Collections.unmodifiableCollection(vars.values());
}
/**
* Returns number of variables in this scope
*/
public int getVarCount() {
return vars.size();
}
/**
* Returns whether this is the global scope.
*/
public boolean isGlobal() {
return parent == null;
}
/**
* Returns whether this is a local scope (i.e. not the global scope).
*/
public boolean isLocal() {
return parent != null;
}
/**
* Gets all variables declared with "var" but without declared types attached.
*/
public Iterator<Var> getDeclarativelyUnboundVarsWithoutTypes() {
return Iterators.filter(
getVars(), DECLARATIVELY_UNBOUND_VARS_WITHOUT_TYPES);
}
static interface TypeResolver {
void resolveTypes();
}
private TypeResolver typeResolver;
/** Resolve all type references. Only used on typed scopes. */
void resolveTypes() {
if (typeResolver != null) {
typeResolver.resolveTypes();
typeResolver = null;
}
}
void setTypeResolver(TypeResolver resolver) {
this.typeResolver = resolver;
}
}
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.javascript.jscomp.NodeTraversal.Callback;
import com.google.javascript.rhino.Node;
/**
* A simple pass to ensure that all AST nodes have line numbers,
* an that the line numbers are monotonically increasing.
*
* @author nicksantos@google.com (Nick Santos)
*/
class LineNumberCheck implements Callback, CompilerPass {
static final DiagnosticType MISSING_LINE_INFO = DiagnosticType.error(
"JSC_MISSING_LINE_INFO",
"No source location information associated with {0}.\n" +
"Most likely a Node has been created with settings the source file " +
"and line/column location. Usually this is done using " +
"Node.copyInformationFrom and supplying a Node from the source AST.");
private final AbstractCompiler compiler;
private boolean requiresLineNumbers = false;
LineNumberCheck(AbstractCompiler compiler) {
this.compiler = compiler;
}
public void setCheckSubTree(Node root) {
requiresLineNumbers = true;
NodeTraversal.traverse(compiler, root, this);
}
@Override
public void process(Node externs, Node root) {
requiresLineNumbers = false;
NodeTraversal.traverse(compiler, root, this);
}
@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
// Each JavaScript file is rooted in a script node, so we'll only
// have line number information inside the script node.
if (n.isScript()) {
requiresLineNumbers
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> = true;
}
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isScript()) {
requiresLineNumbers = false;
} else if (requiresLineNumbers) {
if (n.getLineno() == -1) {
// The tree version of the node is really the best diagnostic
// info we have to offer here.
compiler.report(
t.makeError(n, MISSING_LINE_INFO,
n.toStringTree()));
}
}
}
}
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>
Preconditions.checkNotNull(registerFunction);
return this.registerFunction == registerFunction;
}
boolean isGetterFunction() {
return registerFunction != null;
}
String getName() {
return name;
}
String getExpectedTypeName() {
return expectedTypeName;
}
Node createDefaultValueNode() {
switch (this) {
case REGISTER_BOOLEAN:
return IR.falseNode();
case REGISTER_NUMBER:
return IR.number(0);
case REGISTER_STRING:
return IR.string("");
default:
throw new IllegalStateException();
}
}
}
// A map of function name -> TweakFunction.
private static final Map<String, TweakFunction> TWEAK_FUNCTIONS_MAP;
static {
TWEAK_FUNCTIONS_MAP = Maps.newHashMap();
for (TweakFunction func : TweakFunction.values()) {
TWEAK_FUNCTIONS_MAP.put(func.getName(), func);
}
}
ProcessTweaks(AbstractCompiler compiler, boolean stripTweaks,
Map<String, Node> compilerDefaultValueOverrides) {
this.compiler = compiler;
this.stripTweaks = stripTweaks;
// Having the map sorted is required for the unit tests to be deterministic.
this.compilerDefaultValueOverrides = Maps.newTreeMap();
this.compilerDefaultValueOverrides.putAll(compilerDefaultValueOverrides);
}
@Override
public void process(Node externs, Node root) {
CollectTweaksResult result = collectTweaks(root);
applyCompilerDefaultValueOverrides(result.tweakInfos);
boolean changed = false;
if (stripTweaks) {
changed = stripAllCalls(result.tweakInfos);
} else if (!compilerDefaultValueOverrides.isEmpty()) {
changed = replaceGetCompilerOverridesCalls(result.getOverridesCalls);
}
if (changed) {
compiler.reportCodeChange();
}
}
/**
* Passes the compiler default value overrides to the JS by replacing calls
* to goog.tweak.getCompilerOverrids_ with a map of tweak ID->default value;
*/
private boolean replaceGetCompilerOverridesCalls(
List<TweakFunctionCall> calls) {
for (TweakFunctionCall call : calls) {
Node callNode = call.callNode;
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>
Node objNode = createCompilerDefaultValueOverridesVarNode(callNode);
callNode.getParent().replaceChild(callNode, objNode);
}
return !calls.isEmpty();
}
/**
* Removes all CALL nodes in the given TweakInfos, replacing calls to getter
* functions with the tweak's default value.
*/
private boolean stripAllCalls(Map<String, TweakInfo> tweakInfos) {
for (TweakInfo tweakInfo : tweakInfos.values()) {
boolean isRegistered = tweakInfo.isRegistered();
for (TweakFunctionCall functionCall : tweakInfo.functionCalls) {
Node callNode = functionCall.callNode;
Node parent = callNode.getParent();
if (functionCall.tweakFunc.isGetterFunction()) {
Node newValue;
if (isRegistered) {
newValue = tweakInfo.getDefaultValueNode().cloneNode();
} else {
// When we find a getter of an unregistered tweak, there has
// already been a warning about it, so now just use a default
// value when stripping.
TweakFunction registerFunction =
functionCall.tweakFunc.registerFunction;
newValue = registerFunction.createDefaultValueNode();
}
parent.replaceChild(callNode, newValue);
} else {
Node voidZeroNode = IR.voidNode(IR.number(0).srcref(callNode))
.srcref(callNode);
parent.replaceChild(callNode, voidZeroNode);
}
}
}
return !tweakInfos.isEmpty();
}
/**
* Creates a JS object that holds a map of tweakId -> default value override.
*/
private Node createCompilerDefaultValueOverridesVarNode(
Node sourceInformationNode) {
Node objNode = IR.objectlit().srcref(sourceInformationNode);
for (Entry<String, Node> entry : compilerDefaultValueOverrides.entrySet()) {
Node objKeyNode = IR.stringKey(entry.getKey())
.copyInformationFrom(sourceInformationNode);
Node objValueNode = entry.getValue().cloneNode()
.copyInformationFrom(sourceInformationNode);
objKeyNode.addChildToBack(objValueNode);
objNode.addChildToBack(objKeyNode);
}
return objNode;
}
/** Sets the default values of tweaks based on compiler options. */
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> @SuppressWarnings("incomplete-switch")
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (!n.isCall()) {
return;
}
String callName = n.getFirstChild().getQualifiedName();
TweakFunction tweakFunc = TWEAK_FUNCTIONS_MAP.get(callName);
if (tweakFunc == null) {
return;
}
if (tweakFunc == TweakFunction.GET_COMPILER_OVERRIDES) {
getOverridesCalls.add(
new TweakFunctionCall(t.getSourceName(), tweakFunc, n));
return;
}
// Ensure the first parameter (the tweak ID) is a string literal.
Node tweakIdNode = n.getFirstChild().getNext();
if (!tweakIdNode.isString()) {
compiler.report(t.makeError(tweakIdNode, NON_LITERAL_TWEAK_ID_ERROR));
return;
}
String tweakId = tweakIdNode.getString();
// Make sure there is a TweakInfo structure for it.
TweakInfo tweakInfo = allTweaks.get(tweakId);
if (tweakInfo == null) {
tweakInfo = new TweakInfo(tweakId);
allTweaks.put(tweakId, tweakInfo);
}
switch (tweakFunc) {
case REGISTER_BOOLEAN:
case REGISTER_NUMBER:
case REGISTER_STRING:
// Ensure the ID contains only valid characters.
if (!ID_MATCHER.matchesAllOf(tweakId)) {
compiler.report(t.makeError(tweakIdNode, INVALID_TWEAK_ID_ERROR));
}
// Ensure tweaks are registered in the global scope.
if (!t.inGlobalScope()) {
compiler.report(
t.makeError(n, NON_GLOBAL_TWEAK_INIT_ERROR, tweakId));
break;
}
// Ensure tweaks are registered only once.
if (tweakInfo.isRegistered()) {
compiler.report(
t.makeError(n, TWEAK_MULTIPLY_REGISTERED_ERROR, tweakId));
break;
}
Node tweakDefaultValueNode = tweakIdNode.getNext().getNext();
tweakInfo.addRegisterCall(t.getSourceName(), tweakFunc, n,
tweakDefaultValue
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>/*
* Copyright 2006 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.javascript.rhino.Node;
/**
* This interface defines how objects capable of creating scopes from the parse
* tree behave.
*
*/
interface ScopeCreator {
/**
* Creates a {@link Scope} object.
*
* @param n the root node (either a FUNCTION node, a SCRIPT node, or a
* synthetic block node whose children are all SCRIPT nodes)
* @param parent the parent Scope object (may be null)
*/
Scope createScope(Node n, Scope parent);
}
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> args) {
compiler.report(JSError.make(sourceName, errorRoot, error, args));
}
/**
* Determines whether the given JsDoc info declares a function type.
*/
static boolean isFunctionTypeDeclaration(JSDocInfo info) {
return info.getParameterCount() > 0 ||
info.hasReturnType() ||
info.hasThisType() ||
info.isConstructor() ||
info.isInterface();
}
/**
* The scope that we should declare this function in, if it needs
* to be declared in a scope. Notice that TypedScopeCreator takes
* care of most scope-declaring.
*/
private Scope getScopeDeclaredIn() {
int dotIndex = fnName.indexOf(".");
if (dotIndex != -1) {
String rootVarName = fnName.substring(0, dotIndex);
Var rootVar = scope.getVar(rootVarName);
if (rootVar != null) {
return rootVar.getScope();
}
}
return scope;
}
/**
* Check whether a type is resolvable in the future
* If this has a supertype that hasn't been resolved yet, then we can assume
* this type will be OK once the super type resolves.
* @param objectType
* @return true if objectType is resolvable in the future
*/
private static boolean hasMoreTagsToResolve(ObjectType objectType) {
Preconditions.checkArgument(objectType.isUnknownType());
if (objectType.getImplicitPrototype() != null) {
// constructor extends class
if (objectType.getImplicitPrototype().isResolved()) {
return false;
} else {
return true;
}
} else {
// interface extends interfaces
FunctionType ctor = objectType.getConstructor();
if (ctor != null) {
for (ObjectType interfaceType : ctor.getExtendedInterfaces()) {
if (!interfaceType.isResolved()) {
return true;
}
}
}
return false;
}
}
/** Holds data dynamically inferred about functions. */
static interface FunctionContents {
/** Returns the source node of this function. May be null. */
Node getSourceNode();
/** Returns if the function may be in externs. */
boolean mayBeFromExterns();
/** Returns if a return of
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> warning level.
*/
CheckGlobalNames(AbstractCompiler compiler, CheckLevel level) {
this.compiler = compiler;
this.convention = compiler.getCodingConvention();
this.level = level;
}
/**
* Injects a pre-computed global namespace, so that the same namespace
* can be re-used for multiple check passes. Returns this for easy chaining.
*/
CheckGlobalNames injectNamespace(GlobalNamespace namespace) {
Preconditions.checkArgument(namespace.hasExternsRoot());
this.namespace = namespace;
return this;
}
@Override
public void process(Node externs, Node root) {
if (namespace == null) {
namespace = new GlobalNamespace(compiler, externs, root);
}
// Find prototype properties that will affect our analysis.
Preconditions.checkState(namespace.hasExternsRoot());
findPrototypeProps("Object", objectPrototypeProps);
findPrototypeProps("Function", functionPrototypeProps);
objectPrototypeProps.addAll(
convention.getIndirectlyDeclaredProperties());
for (Name name : namespace.getNameForest()) {
// Skip extern names. Externs are often not runnable as real code,
// and will do things like:
// var x;
// x.method;
// which this check forbids.
if (name.inExterns) {
continue;
}
checkDescendantNames(name, name.globalSets + name.localSets > 0);
}
}
private void findPrototypeProps(String type, Set<String> props) {
Name slot = namespace.getSlot(type);
if (slot != null) {
for (Ref ref : slot.getRefs()) {
if (ref.type == Ref.Type.PROTOTYPE_GET) {
Node fullName = ref.getNode().getParent().getParent();
if (fullName.isGetProp()) {
props.add(fullName.getLastChild().getString());
}
}
}
}
}
/**
* Checks to make sure all the descendants of a name are defined if they
* are referenced.
*
* @param name A global name.
* @param nameIsDefined If true, {@code name} is defined. Otherwise, it's
* undefined, and any references to descendant names should emit warnings.
*/
private void
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> checkDescendantNames(Name name, boolean nameIsDefined) {
if (name.props != null) {
for (Name prop : name.props) {
// if the ancestor of a property is not defined, then we should emit
// warnings for all references to the property.
boolean propIsDefined = false;
if (nameIsDefined) {
// if the ancestor of a property is defined, then let's check that
// the property is also explicitly defined if it needs to be.
propIsDefined = (!propertyMustBeInitializedByFullName(prop) ||
prop.globalSets + prop.localSets > 0);
}
validateName(prop, propIsDefined);
checkDescendantNames(prop, propIsDefined);
}
}
}
private void validateName(Name name, boolean isDefined) {
// If the name is not defined, emit warnings for each reference. While
// we're looking through each reference, check all the module dependencies.
Ref declaration = name.getDeclaration();
Name parent = name.parent;
JSModuleGraph moduleGraph = compiler.getModuleGraph();
for (Ref ref : name.getRefs()) {
// Don't worry about global exprs.
boolean isGlobalExpr = ref.getNode().getParent().isExprResult();
if (!isDefined && !isTypedef(ref)) {
if (!isGlobalExpr) {
reportRefToUndefinedName(name, ref);
}
} else if (declaration != null &&
ref.getModule() != declaration.getModule() &&
!moduleGraph.dependsOn(
ref.getModule(), declaration.getModule())) {
reportBadModuleReference(name, ref);
} else {
// Check for late references.
if (ref.scope.isGlobal()) {
// Prototype references are special, because in our reference graph,
// A.prototype counts as a reference to A.
boolean isPrototypeGet = (ref.type == Ref.Type.PROTOTYPE_GET);
Name owner = isPrototypeGet ? name : parent;
boolean singleGlobalParentDecl =
owner != null &&
owner.getDeclaration() != null &&
owner.localSets == 0;
if (singleGlobalParentDecl &&
owner.getDeclaration().preOrderIndex > ref.preOrderIndex) {
String refName = isPrototypeGet
? name.
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>getFullName() + ".prototype"
: name.getFullName();
compiler.report(
JSError.make(ref.source.getName(), ref.node,
NAME_DEFINED_LATE_WARNING,
refName,
owner.getFullName(),
owner.getDeclaration().source.getName(),
String.valueOf(owner.getDeclaration().node.getLineno())));
}
}
}
}
}
private boolean isTypedef(Ref ref) {
// If this is an annotated EXPR-GET, don't do anything.
Node parent = ref.node.getParent();
if (parent.isExprResult()) {
JSDocInfo info = ref.node.getJSDocInfo();
if (info != null && info.hasTypedefType()) {
return true;
}
}
return false;
}
private void reportBadModuleReference(Name name, Ref ref) {
compiler.report(
JSError.make(ref.source.getName(), ref.node, STRICT_MODULE_DEP_QNAME,
ref.getModule().getName(),
name.getDeclaration().getModule().getName(),
name.getFullName()));
}
private void reportRefToUndefinedName(Name name, Ref ref) {
// grab the highest undefined ancestor to output in the warning message.
while (name.parent != null &&
name.parent.globalSets + name.parent.localSets == 0) {
name = name.parent;
}
compiler.report(
JSError.make(ref.getSourceName(), ref.node, level,
UNDEFINED_NAME_WARNING, name.getFullName()));
}
/**
* Checks whether the given name is a property, and whether that property
* must be initialized with its full qualified name.
*/
private boolean propertyMustBeInitializedByFullName(Name name) {
// If an object or function literal in the global namespace is never
// aliased, then its properties can only come from one of 2 places:
// 1) From its prototype chain, or
// 2) From an assignment to its fully qualified name.
// If we assume #1 is not the case, then #2 implies that its
// properties must all be modeled in the GlobalNamespace as well.
//
// We assume that for global object literals and types (constructors and
//
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> interfaces), we can find all the properties inherited from the prototype
// chain of functions and objects.
if (name.parent == null) {
return false;
}
boolean parentIsAliased = false;
if (name.parent.aliasingGets > 0) {
for (Ref ref : name.parent.getRefs()) {
if (ref.type == Ref.Type.ALIASING_GET) {
Node aliaser = ref.getNode().getParent();
// We don't need to worry about known aliased, because
// they're already covered by the getIndirectlyDeclaredProperties
// call at the top.
boolean isKnownAlias =
aliaser.isCall() &&
(convention.getClassesDefinedByCall(aliaser) != null ||
convention.getSingletonGetterClassName(aliaser) != null);
if (!isKnownAlias) {
parentIsAliased = true;
}
}
}
}
if (parentIsAliased) {
return false;
}
if (objectPrototypeProps.contains(name.getBaseName())) {
return false;
}
if (name.parent.type == Name.Type.OBJECTLIT) {
return true;
}
if (name.parent.type == Name.Type.FUNCTION &&
name.parent.isDeclaredType() &&
!functionPrototypeProps.contains(name.getBaseName())) {
return true;
}
return false;
}
}
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>String propertyName) {
return "superClass_".equals(propertyName) ||
super.isSuperClassReference(propertyName);
}
/**
* Given a qualified name node, returns whether "prototype" is at the end.
* For example:
* a.b.c => false
* a.b.c.prototype => true
*/
private boolean endsWithPrototype(Node qualifiedName) {
return qualifiedName.isGetProp() &&
qualifiedName.getLastChild().getString().equals("prototype");
}
/**
* Extracts X from goog.provide('X'), if the applied Node is goog.
*
* @return The extracted class name, or null.
*/
@Override
public String extractClassNameIfProvide(Node node, Node parent){
return extractClassNameIfGoog(node, parent, "goog.provide");
}
/**
* Extracts X from goog.require('X'), if the applied Node is goog.
*
* @return The extracted class name, or null.
*/
@Override
public String extractClassNameIfRequire(Node node, Node parent){
return extractClassNameIfGoog(node, parent, "goog.require");
}
private static String extractClassNameIfGoog(Node node, Node parent,
String functionName){
String className = null;
if (NodeUtil.isExprCall(parent)) {
Node callee = node.getFirstChild();
if (callee != null && callee.isGetProp()) {
String qualifiedName = callee.getQualifiedName();
if (functionName.equals(qualifiedName)) {
Node target = callee.getNext();
if (target != null && target.isString()) {
className = target.getString();
}
}
}
}
return className;
}
/**
* Use closure's implementation.
* @return closure's function name for exporting properties.
*/
@Override
public String getExportPropertyFunction() {
return "goog.exportProperty";
}
/**
* Use closure's implementation.
* @return closure's function name for exporting symbols.
*/
@Override
public String getExportSymbolFunction() {
return "goog.exportSymbol";
}
@Override
public List<String> identifyTypeDeclarationCall(Node n) {
Node callName = n.getFirstChild();
if ("
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>goog.addDependency".equals(callName.getQualifiedName()) &&
n.getChildCount() >= 3) {
Node typeArray = callName.getNext().getNext();
if (typeArray.isArrayLit()) {
List<String> typeNames = Lists.newArrayList();
for (Node name = typeArray.getFirstChild(); name != null;
name = name.getNext()) {
if (name.isString()) {
typeNames.add(name.getString());
}
}
return typeNames;
}
}
return super.identifyTypeDeclarationCall(n);
}
@Override
public String getAbstractMethodName() {
return "goog.abstractMethod";
}
@Override
public String getSingletonGetterClassName(Node callNode) {
Node callArg = callNode.getFirstChild();
String callName = callArg.getQualifiedName();
// Use both the original name and the post-CollapseProperties name.
if (!("goog.addSingletonGetter".equals(callName) ||
"goog$addSingletonGetter".equals(callName)) ||
callNode.getChildCount() != 2) {
return super.getSingletonGetterClassName(callNode);
}
return callArg.getNext().getQualifiedName();
}
@Override
public void applySingletonGetter(FunctionType functionType,
FunctionType getterType, ObjectType objectType) {
super.applySingletonGetter(functionType, getterType, objectType);
functionType.defineDeclaredProperty("getInstance", getterType,
functionType.getSource());
functionType.defineDeclaredProperty("instance_", objectType,
functionType.getSource());
}
@Override
public String getGlobalObject() {
return "goog.global";
}
private final Set<String> propertyTestFunctions = ImmutableSet.of(
"goog.isDef", "goog.isNull", "goog.isDefAndNotNull",
"goog.isString", "goog.isNumber", "goog.isBoolean",
"goog.isFunction", "goog.isArray", "goog.isObject");
@Override
public boolean isPropertyTestFunction(Node call) {
Preconditions.checkArgument(call.isCall());
return propertyTestFunctions.contains(
call.getFirstChild().getQualifiedName()) ||
super.isPropertyTestFunction(call);
}
@Override
public ObjectLiteralCast getObjectLiteralCast(Node call
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>Node) {
Preconditions.checkArgument(callNode.isCall());
ObjectLiteralCast proxyCast = super.getObjectLiteralCast(callNode);
if (proxyCast != null) {
return proxyCast;
}
Node callName = callNode.getFirstChild();
if (!"goog.reflect.object".equals(callName.getQualifiedName()) ||
callNode.getChildCount() != 3) {
return null;
}
Node typeNode = callName.getNext();
if (!typeNode.isQualifiedName()) {
return null;
}
Node objectNode = typeNode.getNext();
if (!objectNode.isObjectLit()) {
return new ObjectLiteralCast(null, null, OBJECTLIT_EXPECTED);
}
return new ObjectLiteralCast(
typeNode.getQualifiedName(), typeNode.getNext(), null);
}
@Override
public boolean isOptionalParameter(Node parameter) {
return false;
}
@Override
public boolean isVarArgsParameter(Node parameter) {
return false;
}
@Override
public boolean isPrivate(String name) {
return false;
}
@Override
public Collection<AssertionFunctionSpec> getAssertionFunctions() {
return ImmutableList.<AssertionFunctionSpec>of(
new AssertionFunctionSpec("goog.asserts.assert"),
new AssertionFunctionSpec("goog.asserts.assertNumber",
JSTypeNative.NUMBER_TYPE),
new AssertionFunctionSpec("goog.asserts.assertString",
JSTypeNative.STRING_TYPE),
new AssertionFunctionSpec("goog.asserts.assertFunction",
JSTypeNative.FUNCTION_INSTANCE_TYPE),
new AssertionFunctionSpec("goog.asserts.assertObject",
JSTypeNative.OBJECT_TYPE),
new AssertionFunctionSpec("goog.asserts.assertArray",
JSTypeNative.ARRAY_TYPE),
new AssertInstanceofSpec("goog.asserts.assertInstanceof")
);
}
@Override
public Bind describeFunctionBind(Node n, boolean useTypeInfo) {
Bind result = super.describeFunctionBind(n, useTypeInfo);
if (result != null) {
return result;
}
if (!n.isCall()) {
return null;
}
Node callTarget = n.getFirstChild();
String name = callTarget.getQualifiedName();
if (name != null) {
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>Value = objectValue;
}
@Override
public int getIntValue() {
throw new UnsupportedOperationException();
}
@Override
public Object getObjectValue() {
return objectValue;
}
@Override
public String toString() {
return objectValue == null ? "null" : objectValue.toString();
}
@Override
public PropListItem chain(PropListItem next) {
return new ObjectPropListItem(getType(), objectValue, next);
}
}
// A base class for int storing props
private static class IntPropListItem extends AbstractPropListItem {
private static final long serialVersionUID = 1L;
final int intValue;
IntPropListItem(int propType, int intValue, PropListItem next) {
super(propType, next);
this.intValue = intValue;
}
@Override
public int getIntValue() {
return intValue;
}
@Override
public Object getObjectValue() {
throw new UnsupportedOperationException();
}
@Override
public String toString() {
return String.valueOf(intValue);
}
@Override
public PropListItem chain(PropListItem next) {
return new IntPropListItem(getType(), intValue, next);
}
}
public Node(int nodeType) {
type = nodeType;
parent = null;
sourcePosition = -1;
}
public Node(int nodeType, Node child) {
Preconditions.checkArgument(child.parent == null,
"new child has existing parent");
Preconditions.checkArgument(child.next == null,
"new child has existing sibling");
type = nodeType;
parent = null;
first = last = child;
child.next = null;
child.parent = this;
sourcePosition = -1;
}
public Node(int nodeType, Node left, Node right) {
Preconditions.checkArgument(left.parent == null,
"first new child has existing parent");
Preconditions.checkArgument(left.next == null,
"first new child has existing sibling");
Preconditions.checkArgument(right.parent == null,
"second new child has existing parent");
Preconditions.checkArgument(right.next == null,
"second new child has existing sibling");
type = nodeType;
parent = null;
first = left;
last = right;
left.next = right
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>;
left.parent = this;
right.next = null;
right.parent = this;
sourcePosition = -1;
}
public Node(int nodeType, Node left, Node mid, Node right) {
Preconditions.checkArgument(left.parent == null);
Preconditions.checkArgument(left.next == null);
Preconditions.checkArgument(mid.parent == null);
Preconditions.checkArgument(mid.next == null);
Preconditions.checkArgument(right.parent == null);
Preconditions.checkArgument(right.next == null);
type = nodeType;
parent = null;
first = left;
last = right;
left.next = mid;
left.parent = this;
mid.next = right;
mid.parent = this;
right.next = null;
right.parent = this;
sourcePosition = -1;
}
public Node(int nodeType, Node left, Node mid, Node mid2, Node right) {
Preconditions.checkArgument(left.parent == null);
Preconditions.checkArgument(left.next == null);
Preconditions.checkArgument(mid.parent == null);
Preconditions.checkArgument(mid.next == null);
Preconditions.checkArgument(mid2.parent == null);
Preconditions.checkArgument(mid2.next == null);
Preconditions.checkArgument(right.parent == null);
Preconditions.checkArgument(right.next == null);
type = nodeType;
parent = null;
first = left;
last = right;
left.next = mid;
left.parent = this;
mid.next = mid2;
mid.parent = this;
mid2.next = right;
mid2.parent = this;
right.next = null;
right.parent = this;
sourcePosition = -1;
}
public Node(int nodeType, int lineno, int charno) {
type = nodeType;
parent = null;
sourcePosition = mergeLineCharNo(lineno, charno);
}
public Node(int nodeType, Node child, int lineno, int charno) {
this(nodeType, child);
sourcePosition = mergeLineCharNo(lineno, charno);
}
public Node(int nodeType, Node left, Node right, int lineno, int charno) {
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>
this(nodeType, left, right);
sourcePosition = mergeLineCharNo(lineno, charno);
}
public Node(int nodeType, Node left, Node mid, Node right,
int lineno, int charno) {
this(nodeType, left, mid, right);
sourcePosition = mergeLineCharNo(lineno, charno);
}
public Node(int nodeType, Node left, Node mid, Node mid2, Node right,
int lineno, int charno) {
this(nodeType, left, mid, mid2, right);
sourcePosition = mergeLineCharNo(lineno, charno);
}
public Node(int nodeType, Node[] children, int lineno, int charno) {
this(nodeType, children);
sourcePosition = mergeLineCharNo(lineno, charno);
}
public Node(int nodeType, Node[] children) {
this.type = nodeType;
parent = null;
if (children.length != 0) {
this.first = children[0];
this.last = children[children.length - 1];
for (int i = 1; i < children.length; i++) {
if (null != children[i - 1].next) {
// fail early on loops. implies same node in array twice
throw new IllegalArgumentException("duplicate child");
}
children[i - 1].next = children[i];
Preconditions.checkArgument(children[i - 1].parent == null);
children[i - 1].parent = this;
}
Preconditions.checkArgument(children[children.length - 1].parent == null);
children[children.length - 1].parent = this;
if (null != this.last.next) {
// fail early on loops. implies same node in array twice
throw new IllegalArgumentException("duplicate child");
}
}
}
public static Node newNumber(double number) {
return new NumberNode(number);
}
public static Node newNumber(double number, int lineno, int charno) {
return new NumberNode(number, lineno, charno);
}
public static Node newString(String str) {
return new StringNode(Token.STRING, str);
}
public static Node newString(int type, String str) {
return
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> new StringNode(type, str);
}
public static Node newString(String str, int lineno, int charno) {
return new StringNode(Token.STRING, str, lineno, charno);
}
public static Node newString(int type, String str, int lineno, int charno) {
return new StringNode(type, str, lineno, charno);
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public boolean hasChildren() {
return first != null;
}
public Node getFirstChild() {
return first;
}
public Node getLastChild() {
return last;
}
public Node getNext() {
return next;
}
public Node getChildBefore(Node child) {
if (child == first) {
return null;
}
Node n = first;
while (n.next != child) {
n = n.next;
if (n == null) {
throw new RuntimeException("node is not a child");
}
}
return n;
}
public Node getChildAtIndex(int i) {
Node n = first;
while (i > 0) {
n = n.next;
i--;
}
return n;
}
public int getIndexOfChild(Node child) {
Node n = first;
int i = 0;
while (n != null) {
if (child == n) {
return i;
}
n = n.next;
i++;
}
return -1;
}
public Node getLastSibling() {
Node n = this;
while (n.next != null) {
n = n.next;
}
return n;
}
public void addChildToFront(Node child) {
Preconditions.checkArgument(child.parent == null);
Preconditions.checkArgument(child.next == null);
child.parent = this;
child.next = first;
first = child;
if (last == null) {
last = child;
}
}
public void addChildToBack(Node child) {
Preconditions.checkArgument(child.parent == null);
Preconditions.checkArgument(child.next == null);
child.parent = this;
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>
child.next = null;
if (last == null) {
first = last = child;
return;
}
last.next = child;
last = child;
}
public void addChildrenToFront(Node children) {
for (Node child = children; child != null; child = child.next) {
Preconditions.checkArgument(child.parent == null);
child.parent = this;
}
Node lastSib = children.getLastSibling();
lastSib.next = first;
first = children;
if (last == null) {
last = lastSib;
}
}
public void addChildrenToBack(Node children) {
addChildrenAfter(children, getLastChild());
}
/**
* Add 'child' before 'node'.
*/
public void addChildBefore(Node newChild, Node node) {
Preconditions.checkArgument(node != null && node.parent == this,
"The existing child node of the parent should not be null.");
Preconditions.checkArgument(newChild.next == null,
"The new child node has siblings.");
Preconditions.checkArgument(newChild.parent == null,
"The new child node already has a parent.");
if (first == node) {
newChild.parent = this;
newChild.next = first;
first = newChild;
return;
}
Node prev = getChildBefore(node);
addChildAfter(newChild, prev);
}
/**
* Add 'child' after 'node'.
*/
public void addChildAfter(Node newChild, Node node) {
Preconditions.checkArgument(newChild.next == null,
"The new child node has siblings.");
addChildrenAfter(newChild, node);
}
/**
* Add all children after 'node'.
*/
public void addChildrenAfter(Node children, Node node) {
Preconditions.checkArgument(node == null || node.parent == this);
for (Node child = children; child != null; child = child.next) {
Preconditions.checkArgument(child.parent == null);
child.parent = this;
}
Node lastSibling = children.getLastSibling();
if (node != null) {
Node oldNext = node.next;
node.next = children;
lastSibling.
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>next = oldNext;
if (node == last) {
last = lastSibling;
}
} else {
// Append to the beginning.
if (first != null) {
lastSibling.next = first;
} else {
last = lastSibling;
}
first = children;
}
}
/**
* Detach a child from its parent and siblings.
*/
public void removeChild(Node child) {
Node prev = getChildBefore(child);
if (prev == null) {
first = first.next;
} else {
prev.next = child.next;
}
if (child == last) {
last = prev;
}
child.next = null;
child.parent = null;
}
/**
* Detaches child from Node and replaces it with newChild.
*/
public void replaceChild(Node child, Node newChild) {
Preconditions.checkArgument(newChild.next == null,
"The new child node has siblings.");
Preconditions.checkArgument(newChild.parent == null,
"The new child node already has a parent.");
// Copy over important information.
newChild.copyInformationFrom(child);
newChild.next = child.next;
newChild.parent = this;
if (child == first) {
first = newChild;
} else {
Node prev = getChildBefore(child);
prev.next = newChild;
}
if (child == last) {
last = newChild;
}
child.next = null;
child.parent = null;
}
public void replaceChildAfter(Node prevChild, Node newChild) {
Preconditions.checkArgument(prevChild.parent == this,
"prev is not a child of this node.");
Preconditions.checkArgument(newChild.next == null,
"The new child node has siblings.");
Preconditions.checkArgument(newChild.parent == null,
"The new child node already has a parent.");
// Copy over important information.
newChild.copyInformationFrom(prevChild);
Node child = prevChild.next;
newChild.next = child.next;
newChild.parent = this;
prevChild.next = newChild;
if (child == last) {
last = newChild;
}
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> child.next = null;
child.parent = null;
}
@VisibleForTesting
PropListItem lookupProperty(int propType) {
PropListItem x = propListHead;
while (x != null && propType != x.getType()) {
x = x.getNext();
}
return x;
}
/**
* Clone the properties from the provided node without copying
* the property object. The receiving node may not have any
* existing properties.
* @param other The node to clone properties from.
* @return this node.
*/
public Node clonePropsFrom(Node other) {
Preconditions.checkState(this.propListHead == null,
"Node has existing properties.");
this.propListHead = other.propListHead;
return this;
}
public void removeProp(int propType) {
PropListItem result = removeProp(propListHead, propType);
if (result != propListHead) {
propListHead = result;
}
}
/**
* @param item The item to inspect
* @param propType The property to look for
* @return The replacement list if the property was removed, or
* 'item' otherwise.
*/
private PropListItem removeProp(PropListItem item, int propType) {
if (item == null) {
return null;
} else if (item.getType() == propType) {
return item.getNext();
} else {
PropListItem result = removeProp(item.getNext(), propType);
if (result != item.getNext()) {
return item.chain(result);
} else {
return item;
}
}
}
public Object getProp(int propType) {
PropListItem item = lookupProperty(propType);
if (item == null) {
return null;
}
return item.getObjectValue();
}
public boolean getBooleanProp(int propType) {
return getIntProp(propType) != 0;
}
/**
* Returns the integer value for the property, or 0 if the property
* is not defined.
*/
public int getIntProp(int propType) {
PropListItem item = lookupProperty(propType);
if (item == null) {
return 0;
}
return item.getIntValue();
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> how many of the lower-order bits of
* sourcePosition are reserved for storing the column number.
* Bits above these store the line number.
* This gives us decent position information for everything except
* files already passed through a minimizer, where lines might
* be longer than 4096 characters.
*/
public static final int COLUMN_BITS = 12;
/**
* MAX_COLUMN_NUMBER represents the maximum column number that can
* be represented. JSCompiler's modifications to Rhino cause all
* tokens located beyond the maximum column to MAX_COLUMN_NUMBER.
*/
public static final int MAX_COLUMN_NUMBER = (1 << COLUMN_BITS) - 1;
/**
* COLUMN_MASK stores a value where bits storing the column number
* are set, and bits storing the line are not set. It's handy for
* separating column number from line number.
*/
public static final int COLUMN_MASK = MAX_COLUMN_NUMBER;
/**
* Source position of this node. The position is encoded with the
* column number in the low 12 bits of the integer, and the line
* number in the rest. Create some handy constants so we can change this
* size if we want.
*/
private int sourcePosition;
private JSType jsType;
private Node parent;
//==========================================================================
// Source position management
public void setStaticSourceFile(StaticSourceFile file) {
this.putProp(STATIC_SOURCE_FILE, file);
}
/** Sets the source file to a non-extern file of the given name. */
public void setSourceFileForTesting(String name) {
this.putProp(STATIC_SOURCE_FILE, new SimpleSourceFile(name, false));
}
public String getSourceFileName() {
StaticSourceFile file = getStaticSourceFile();
return file == null ? null : file.getName();
}
/** Returns the source file associated with this input. May be null */
public StaticSourceFile getStaticSourceFile() {
return ((StaticSourceFile) this.getProp(STATIC_SOURCE_FILE));
}
/**
* @param inputId
*/
public void setInputId(InputId inputId) {
this.putProp(INPUT_ID, inputId);
}
/**
* @return The Id of the CompilerInput associated with this Node.
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> Iterable<Node>, Iterator<Node> {
private final Node start;
private Node current;
private boolean used;
SiblingNodeIterable(Node start) {
this.start = start;
this.current = start;
this.used = false;
}
@Override
public Iterator<Node> iterator() {
if (!used) {
used = true;
return this;
} else {
// We have already used the current object as an iterator;
// we must create a new SiblingNodeIterable based on this
// iterable's start node.
//
// Since the primary use case for Node.children is in for
// loops, this branch is extremely unlikely.
return (new SiblingNodeIterable(start)).iterator();
}
}
@Override
public boolean hasNext() {
return current != null;
}
@Override
public Node next() {
if (current == null) {
throw new NoSuchElementException();
}
try {
return current;
} finally {
current = current.getNext();
}
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
// ==========================================================================
// Accessors
PropListItem getPropListHeadForTesting() {
return propListHead;
}
public Node getParent() {
return parent;
}
/**
* Gets the ancestor node relative to this.
*
* @param level 0 = this, 1 = the parent, etc.
*/
public Node getAncestor(int level) {
Preconditions.checkArgument(level >= 0);
Node node = this;
while (node != null && level-- > 0) {
node = node.getParent();
}
return node;
}
/**
* Iterates all of the node's ancestors excluding itself.
*/
public AncestorIterable getAncestors() {
return new AncestorIterable(this.getParent());
}
/**
* Iterator to go up the ancestor tree.
*/
public static class AncestorIterable implements Iterable<Node> {
private Node cur;
/**
* @param cur The node to start.
*/
AncestorIterable(Node cur) {
this.cur = cur;
}
@Override
public Iterator<Node> iterator() {
return new Iterator<Node>() {
@Override
public boolean has
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>Next() {
return cur != null;
}
@Override
public Node next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
Node n = cur;
cur = cur.getParent();
return n;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
}
/**
* Check for one child more efficiently than by iterating over all the
* children as is done with Node.getChildCount().
*
* @return Whether the node has exactly one child.
*/
public boolean hasOneChild() {
return first != null && first == last;
}
/**
* Check for more than one child more efficiently than by iterating over all
* the children as is done with Node.getChildCount().
*
* @return Whether the node more than one child.
*/
public boolean hasMoreThanOneChild() {
return first != null && first != last;
}
public int getChildCount() {
int c = 0;
for (Node n = first; n != null; n = n.next) {
c++;
}
return c;
}
// Intended for testing and verification only.
public boolean hasChild(Node child) {
for (Node n = first; n != null; n = n.getNext()) {
if (child == n) {
return true;
}
}
return false;
}
/**
* Checks if the subtree under this node is the same as another subtree.
* Returns null if it's equal, or a message describing the differences.
*/
public String checkTreeEquals(Node node2) {
NodeMismatch diff = checkTreeEqualsImpl(node2);
if (diff != null) {
return "Node tree inequality:" +
"\nTree1:\n" + toStringTree() +
"\n\nTree2:\n" + node2.toStringTree() +
"\n\nSubtree1: " + diff.nodeA.toStringTree() +
"\n\nSubtree2: " + diff.nodeB.toStringTree();
}
return null;
}
/**
* Compare this node to node2 recursively and return the first pair of nodes
* that differs doing a preorder depth-first traversal. Package private for
* testing.
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> by dots. If the node ultimately under the left
* sub-tree is not a simple name, this is not a valid qualified name.
*
* @return a null if this is not a qualified name, or a dot-separated string
* of the name and properties.
*/
public String getQualifiedName() {
if (type == Token.NAME) {
String name = getString();
return name.isEmpty() ? null : name;
} else if (type == Token.GETPROP) {
String left = getFirstChild().getQualifiedName();
if (left == null) {
return null;
}
return left + "." + getLastChild().getString();
} else if (type == Token.THIS) {
return "this";
} else {
return null;
}
}
/**
* Returns whether a node corresponds to a simple or a qualified name, such as
* <code>x</code> or <code>a.b.c</code> or <code>this.a</code>.
*/
public boolean isQualifiedName() {
switch (getType()) {
case Token.NAME:
return getString().isEmpty() ? false : true;
case Token.THIS:
return true;
case Token.GETPROP:
return getFirstChild().isQualifiedName();
default:
return false;
}
}
/**
* Returns whether a node corresponds to a simple or a qualified name without
* a "this" reference, such as <code>a.b.c</code>, but not <code>this.a</code>
* .
*/
public boolean isUnscopedQualifiedName() {
switch (getType()) {
case Token.NAME:
return getString().isEmpty() ? false : true;
case Token.GETPROP:
return getFirstChild().isUnscopedQualifiedName();
default:
return false;
}
}
// ==========================================================================
// Mutators
/**
* Removes this node from its parent. Equivalent to:
* node.getParent().removeChild();
*/
public Node detachFromParent() {
Preconditions.checkState(parent != null);
parent.removeChild(this);
return this;
}
/**
* Removes the first child of Node. Equivalent to:
* node.removeChild(node.getFirstChild());
*
* @return The removed Node.
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> */
public Node removeFirstChild() {
Node child = first;
if (child != null) {
removeChild(child);
}
return child;
}
/**
* @return A Node that is the head of the list of children.
*/
public Node removeChildren() {
Node children = first;
for (Node child = first; child != null; child = child.getNext()) {
child.parent = null;
}
first = null;
last = null;
return children;
}
/**
* Removes all children from this node and isolates the children from each
* other.
*/
public void detachChildren() {
for (Node child = first; child != null;) {
Node nextChild = child.getNext();
child.parent = null;
child.next = null;
child = nextChild;
}
first = null;
last = null;
}
public Node removeChildAfter(Node prev) {
Preconditions.checkArgument(prev.parent == this,
"prev is not a child of this node.");
Preconditions.checkArgument(prev.next != null,
"no next sibling.");
Node child = prev.next;
prev.next = child.next;
if (child == last) {
last = prev;
}
child.next = null;
child.parent = null;
return child;
}
/**
* @return A detached clone of the Node, specifically excluding its children.
*/
public Node cloneNode() {
Node result;
try {
result = (Node) super.clone();
// PropListItem lists are immutable and can be shared so there is no
// need to clone them here.
result.next = null;
result.first = null;
result.last = null;
result.parent = null;
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e.getMessage());
}
return result;
}
/**
* @return A detached clone of the Node and all its children.
*/
public Node cloneTree() {
Node result = cloneNode();
for (Node n2 = getFirstChild(); n2 != null; n2 = n2.getNext()) {
Node n2clone = n2.cloneTree();
n2clone.parent = result;
if
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> (result.last != null) {
result.last.next = n2clone;
}
if (result.first == null) {
result.first = n2clone;
}
result.last = n2clone;
}
return result;
}
/**
* Copies source file and name information from the other
* node given to the current node. Used for maintaining
* debug information across node append and remove operations.
* @return this
*/
// TODO(nicksantos): The semantics of this method are ill-defined. Delete it.
public Node copyInformationFrom(Node other) {
if (getProp(ORIGINALNAME_PROP) == null) {
putProp(ORIGINALNAME_PROP, other.getProp(ORIGINALNAME_PROP));
}
if (getProp(STATIC_SOURCE_FILE) == null) {
putProp(STATIC_SOURCE_FILE, other.getProp(STATIC_SOURCE_FILE));
sourcePosition = other.sourcePosition;
}
return this;
}
/**
* Copies source file and name information from the other node to the
* entire tree rooted at this node.
* @return this
*/
// TODO(nicksantos): The semantics of this method are ill-defined. Delete it.
public Node copyInformationFromForTree(Node other) {
copyInformationFrom(other);
for (Node child = getFirstChild();
child != null; child = child.getNext()) {
child.copyInformationFromForTree(other);
}
return this;
}
/**
* Overwrite all the source information in this node with
* that of {@code other}.
*/
public Node useSourceInfoFrom(Node other) {
putProp(ORIGINALNAME_PROP, other.getProp(ORIGINALNAME_PROP));
putProp(STATIC_SOURCE_FILE, other.getProp(STATIC_SOURCE_FILE));
sourcePosition = other.sourcePosition;
return this;
}
public Node srcref(Node other) {
return useSourceInfoFrom(other);
}
/**
* Overwrite all the source information in this node and its subtree with
* that of {@code other}.
*/
public Node useSourceInfoFromForTree(Node other) {
useSourceInfoFrom(other);
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> exception
// locality of the result
// We want a value of 0 to mean "global state changes and
// unknown locality of result".
public static final int FLAG_GLOBAL_STATE_UNMODIFIED = 1;
public static final int FLAG_THIS_UNMODIFIED = 2;
public static final int FLAG_ARGUMENTS_UNMODIFIED = 4;
public static final int FLAG_NO_THROWS = 8;
public static final int FLAG_LOCAL_RESULTS = 16;
public static final int SIDE_EFFECTS_FLAGS_MASK = 31;
public static final int SIDE_EFFECTS_ALL = 0;
public static final int NO_SIDE_EFFECTS =
FLAG_GLOBAL_STATE_UNMODIFIED
| FLAG_THIS_UNMODIFIED
| FLAG_ARGUMENTS_UNMODIFIED
| FLAG_NO_THROWS;
/**
* Marks this function or constructor call's side effect flags.
* This property is only meaningful for {@link Token#CALL} and
* {@link Token#NEW} nodes.
*/
public void setSideEffectFlags(int flags) {
Preconditions.checkArgument(
getType() == Token.CALL || getType() == Token.NEW,
"setIsNoSideEffectsCall only supports CALL and NEW nodes, got " +
Token.name(getType()));
putIntProp(SIDE_EFFECT_FLAGS, flags);
}
public void setSideEffectFlags(SideEffectFlags flags) {
setSideEffectFlags(flags.valueOf());
}
/**
* Returns the side effects flags for this node.
*/
public int getSideEffectFlags() {
return getIntProp(SIDE_EFFECT_FLAGS);
}
/**
* A helper class for getting and setting the side-effect flags.
* @author johnlenz@google.com (John Lenz)
*/
public static class SideEffectFlags {
private int value = Node.SIDE_EFFECTS_ALL;
public SideEffectFlags() {
}
public SideEffectFlags(int value) {
this.value = value;
}
public int valueOf() {
return value;
}
/** All side-effect occur and the returned results are non-local. */
public SideEffectFlags setAllFlags() {
value = Node.
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>
public int hashCode() {
return Objects.hashCode(nodeA, nodeB);
}
}
/*** AST type check methods ***/
public boolean isAdd() {
return this.getType() == Token.ADD;
}
public boolean isAnd() {
return this.getType() == Token.AND;
}
public boolean isArrayLit() {
return this.getType() == Token.ARRAYLIT;
}
public boolean isAssign() {
return this.getType() == Token.ASSIGN;
}
public boolean isAssignAdd() {
return this.getType() == Token.ASSIGN_ADD;
}
public boolean isBlock() {
return this.getType() == Token.BLOCK;
}
public boolean isBreak() {
return this.getType() == Token.BREAK;
}
public boolean isCall() {
return this.getType() == Token.CALL;
}
public boolean isCase() {
return this.getType() == Token.CASE;
}
public boolean isCast() {
return this.getType() == Token.CAST;
}
public boolean isCatch() {
return this.getType() == Token.CATCH;
}
public boolean isComma() {
return this.getType() == Token.COMMA;
}
public boolean isContinue() {
return this.getType() == Token.CONTINUE;
}
public boolean isDebugger() {
return this.getType() == Token.DEBUGGER;
}
public boolean isDec() {
return this.getType() == Token.DEC;
}
public boolean isDefaultCase() {
return this.getType() == Token.DEFAULT_CASE;
}
public boolean isDelProp() {
return this.getType() == Token.DELPROP;
}
public boolean isDo() {
return this.getType() == Token.DO;
}
public boolean isEmpty() {
return this.getType() == Token.EMPTY;
}
public boolean isExprResult() {
return this.getType() == Token.EXPR_RESULT;
}
public boolean isFalse() {
return this.getType() == Token.FALSE;
}
public boolean isFor() {
return this.getType() == Token.FOR;
}
public boolean isFunction() {
return this.getType() == Token.FUNCTION;
}
public boolean isGetterDef
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>() {
return this.getType() == Token.GETTER_DEF;
}
public boolean isGetElem() {
return this.getType() == Token.GETELEM;
}
public boolean isGetProp() {
return this.getType() == Token.GETPROP;
}
public boolean isHook() {
return this.getType() == Token.HOOK;
}
public boolean isIf() {
return this.getType() == Token.IF;
}
public boolean isIn() {
return this.getType() == Token.IN;
}
public boolean isInc() {
return this.getType() == Token.INC;
}
public boolean isInstanceOf() {
return this.getType() == Token.INSTANCEOF;
}
public boolean isLabel() {
return this.getType() == Token.LABEL;
}
public boolean isLabelName() {
return this.getType() == Token.LABEL_NAME;
}
public boolean isName() {
return this.getType() == Token.NAME;
}
public boolean isNE() {
return this.getType() == Token.NE;
}
public boolean isNew() {
return this.getType() == Token.NEW;
}
public boolean isNot() {
return this.getType() == Token.NOT;
}
public boolean isNull() {
return this.getType() == Token.NULL;
}
public boolean isNumber() {
return this.getType() == Token.NUMBER;
}
public boolean isObjectLit() {
return this.getType() == Token.OBJECTLIT;
}
public boolean isOr() {
return this.getType() == Token.OR;
}
public boolean isParamList() {
return this.getType() == Token.PARAM_LIST;
}
public boolean isRegExp() {
return this.getType() == Token.REGEXP;
}
public boolean isReturn() {
return this.getType() == Token.RETURN;
}
public boolean isScript() {
return this.getType() == Token.SCRIPT;
}
public boolean isSetterDef() {
return this.getType() == Token.SETTER_DEF;
}
public boolean isString() {
return this.getType() == Token.STRING;
}
public boolean isStringKey() {
return this.getType()
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> private final Map<String, Node> ctors = Maps.newHashMap();
private final CodingConvention convention;
CheckProvidesCallback(CodingConvention convention){
this.convention = convention;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.CALL:
String providedClassName =
codingConvention.extractClassNameIfProvide(n, parent);
if (providedClassName != null) {
provides.put(providedClassName, n);
}
break;
case Token.FUNCTION:
visitFunctionNode(n, parent);
break;
case Token.SCRIPT:
visitScriptNode();
}
}
private void visitFunctionNode(Node n, Node parent) {
Node name = null;
JSDocInfo info = parent.getJSDocInfo();
if (info != null && info.isConstructor()) {
name = parent.getFirstChild();
} else {
// look to the child, maybe it's a named function
info = n.getJSDocInfo();
if (info != null && info.isConstructor()) {
name = n.getFirstChild();
}
}
if (name != null && name.isQualifiedName()) {
String qualifiedName = name.getQualifiedName();
if (!this.convention.isPrivate(qualifiedName)) {
Visibility visibility = info.getVisibility();
if (!visibility.equals(JSDocInfo.Visibility.PRIVATE)) {
ctors.put(qualifiedName, name);
}
}
}
}
private void visitScriptNode() {
for (Map.Entry<String, Node> ctorEntry : ctors.entrySet()) {
String ctor = ctorEntry.getKey();
int index = -1;
boolean found = false;
do {
index = ctor.indexOf('.', index + 1);
String provideKey = index == -1 ? ctor : ctor.substring(0, index);
if (provides.containsKey(provideKey)) {
found = true;
break;
}
} while (index != -1);
if (!found) {
Node n = ctorEntry.getValue();
compiler.report(
JSError.make(n.getSourceFileName(), n,
checkLevel, MISSING_PROVIDE_WARNING, ctorEntry.getKey()));
}
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> {
@Override
public void visit(AbstractCompiler compiler, Node root) {
if (root.isFunction()) {
root = root.getLastChild();
}
do {
handler.reset();
NodeTraversal.traverse(compiler, root, new PeepCallback());
} while (retraverseOnChange && handler.hasCodeChanged());
}
});
endTraversal();
compiler.removeChangeHandler(handler);
}
private class PeepCallback extends AbstractShallowCallback {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
Node currentNode = n, newNode;
boolean codeChanged = false;
do {
codeChanged = false;
for (AbstractPeepholeOptimization optim : peepholeOptimizations) {
newNode = optim.optimizeSubtree(currentNode);
if (newNode != currentNode) {
codeChanged = true;
currentNode = newNode;
}
if (currentNode == null) {
return;
}
}
} while(codeChanged);
}
}
/**
* Make sure that all the optimizations have the current traversal so they
* can report errors.
*/
private void beginTraversal() {
for (AbstractPeepholeOptimization optimization : peepholeOptimizations) {
optimization.beginTraversal(compiler);
}
}
private void endTraversal() {
for (AbstractPeepholeOptimization optimization : peepholeOptimizations) {
optimization.endTraversal(compiler);
}
}
}
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>_TYPE);
registerNativeType(JSTypeNative.GREATEST_FUNCTION_TYPE,
GREATEST_FUNCTION_TYPE);
// Register the prototype property. See the comments below in
// registerPropertyOnType about the bootstrapping process.
registerPropertyOnType("prototype", OBJECT_FUNCTION_TYPE);
}
private void initializeRegistry() {
register(getNativeType(JSTypeNative.ARRAY_TYPE));
register(getNativeType(JSTypeNative.BOOLEAN_OBJECT_TYPE));
register(getNativeType(JSTypeNative.BOOLEAN_TYPE));
register(getNativeType(JSTypeNative.DATE_TYPE));
register(getNativeType(JSTypeNative.NULL_TYPE));
register(getNativeType(JSTypeNative.NULL_TYPE), "Null");
register(getNativeType(JSTypeNative.NUMBER_OBJECT_TYPE));
register(getNativeType(JSTypeNative.NUMBER_TYPE));
register(getNativeType(JSTypeNative.OBJECT_TYPE));
register(getNativeType(JSTypeNative.ERROR_TYPE));
register(getNativeType(JSTypeNative.URI_ERROR_TYPE));
register(getNativeType(JSTypeNative.EVAL_ERROR_TYPE));
register(getNativeType(JSTypeNative.TYPE_ERROR_TYPE));
register(getNativeType(JSTypeNative.RANGE_ERROR_TYPE));
register(getNativeType(JSTypeNative.REFERENCE_ERROR_TYPE));
register(getNativeType(JSTypeNative.SYNTAX_ERROR_TYPE));
register(getNativeType(JSTypeNative.REGEXP_TYPE));
register(getNativeType(JSTypeNative.STRING_OBJECT_TYPE));
register(getNativeType(JSTypeNative.STRING_TYPE));
register(getNativeType(JSTypeNative.VOID_TYPE));
register(getNativeType(JSTypeNative.VOID_TYPE), "Undefined");
register(getNativeType(JSTypeNative.VOID_TYPE), "void");
register(getNativeType(JSTypeNative.FUNCTION_INSTANCE_TYPE), "Function");
}
private void register(JSType type) {
register(type, type.toString());
}
private void register(JSType type, String name) {
Preconditions.checkArgument(
!name.contains("<"), "Type names
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> example, traversing an IF node as root will visit the two subtrees
* pointed by the {@link ControlFlowGraph.Branch#ON_TRUE} and
* {@link ControlFlowGraph.Branch#ON_FALSE} edges.
*/
public abstract static class AbstractCfgNodeTraversalCallback implements
Callback {
@Override
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
if (parent == null) {
return true;
}
return !isEnteringNewCfgNode(n);
}
}
/**
* @return True if n should be represented by a new CFG node in the control
* flow graph.
*/
public static boolean isEnteringNewCfgNode(Node n) {
Node parent = n.getParent();
switch (parent.getType()) {
case Token.BLOCK:
case Token.SCRIPT:
case Token.TRY:
return true;
case Token.FUNCTION:
// A function node represents the start of a function where the name
// bleeds into the local scope and parameters are assigned
// to the formal argument names. The node includes the name of the
// function and the LP list since we assume the whole set up process
// is atomic without change in control flow. The next change of
// control is going into the function's body, represented by the second
// child.
return n != parent.getFirstChild().getNext();
case Token.WHILE:
case Token.DO:
case Token.IF:
// These control structures are represented by a node that holds the
// condition. Each of them is a branch node based on its condition.
return NodeUtil.getConditionExpression(parent) != n;
case Token.FOR:
// The FOR(;;) node differs from other control structures in that
// it has an initialization and an increment statement. Those
// two statements have corresponding CFG nodes to represent them.
// The FOR node only represents the condition check for each iteration.
// That way the following:
// for(var x = 0; x < 10; x++) { } has a graph that is isomorphic to
// var x = 0; while(x<10) { x++; }
if (NodeUtil.isForIn(parent)) {
// TODO(user): Investigate how we should handle the case where
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>
// we have a very complex expression inside the FOR-IN header.
return n != parent.getFirstChild();
} else {
return NodeUtil.getConditionExpression(parent) != n;
}
case Token.SWITCH:
case Token.CASE:
case Token.CATCH:
case Token.WITH:
return n != parent.getFirstChild();
default:
return false;
}
}
@Override
public String toString() {
String s = "CFG:\n";
for (GraphvizEdge e : getGraphvizEdges()) {
s += e.toString() + '\n';
}
return s;
}
}
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>, JSType type) {
Preconditions.checkNotNull(node);
Preconditions.checkNotNull(type);
this.node = node;
this.type = type;
// Other parts of this pass may read off the node.
// (like when we set the LHS of an assign with a typed RHS function.)
node.setJSType(type);
}
void resolve(Scope scope) {
node.setJSType(type.resolve(typeParsingErrorReporter, scope));
}
}
TypedScopeCreator(AbstractCompiler compiler) {
this(compiler, compiler.getCodingConvention());
}
TypedScopeCreator(AbstractCompiler compiler,
CodingConvention codingConvention) {
this.compiler = compiler;
this.validator = compiler.getTypeValidator();
this.codingConvention = codingConvention;
this.typeRegistry = compiler.getTypeRegistry();
this.typeParsingErrorReporter = typeRegistry.getErrorReporter();
this.unknownType = typeRegistry.getNativeObjectType(UNKNOWN_TYPE);
}
/**
* Creates a scope with all types declared. Declares newly discovered types
* and type properties in the type registry.
*/
@Override
public Scope createScope(Node root, Scope parent) {
// Constructing the global scope is very different than constructing
// inner scopes, because only global scopes can contain named classes that
// show up in the type registry.
Scope newScope = null;
AbstractScopeBuilder scopeBuilder = null;
if (parent == null) {
JSType globalThis =
typeRegistry.getNativeObjectType(JSTypeNative.GLOBAL_THIS);
// Mark the main root, the externs root, and the src root
// with the global this type.
root.setJSType(globalThis);
root.getFirstChild().setJSType(globalThis);
root.getLastChild().setJSType(globalThis);
// Run a first-order analysis over the syntax tree.
(new FirstOrderFunctionAnalyzer(compiler, functionAnalysisResults))
.process(root.getFirstChild(), root.getLastChild());
// Find all the classes in the global scope.
newScope = createInitialScope(root);
GlobalScopeBuilder globalScopeBuilder = new GlobalScopeBuilder(newScope);
scopeBuilder = globalScopeBuilder;
NodeTraversal.traverse(compiler, root, scopeBuilder);
} else {
new
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>Scope = new Scope(parent, root);
LocalScopeBuilder localScopeBuilder = new LocalScopeBuilder(newScope);
scopeBuilder = localScopeBuilder;
localScopeBuilder.build();
}
scopeBuilder.resolveStubDeclarations();
// Gather the properties in each function that we found in the
// global scope, if that function has a @this type that we can
// build properties on.
for (Node functionNode : scopeBuilder.nonExternFunctions) {
JSType type = functionNode.getJSType();
if (type != null && type.isFunctionType()) {
FunctionType fnType = type.toMaybeFunctionType();
JSType fnThisType = fnType.getTypeOfThis();
if (!fnThisType.isUnknownType()) {
NodeTraversal.traverse(compiler, functionNode.getLastChild(),
scopeBuilder.new CollectProperties(fnThisType));
}
}
}
if (parent == null) {
codingConvention.defineDelegateProxyPrototypeProperties(
typeRegistry, newScope, delegateProxyPrototypes,
delegateCallingConventions);
}
newScope.setTypeResolver(scopeBuilder);
return newScope;
}
/**
* Patches a given global scope by removing variables previously declared in
* a script and re-traversing a new version of that script.
*
* @param globalScope The global scope generated by {@code createScope}.
* @param scriptRoot The script that is modified.
*/
void patchGlobalScope(Scope globalScope, Node scriptRoot) {
// Preconditions: This is supposed to be called only on (named) SCRIPT nodes
// and a global typed scope should have been generated already.
Preconditions.checkState(scriptRoot.isScript());
Preconditions.checkNotNull(globalScope);
Preconditions.checkState(globalScope.isGlobal());
String scriptName = NodeUtil.getSourceName(scriptRoot);
Preconditions.checkNotNull(scriptName);
for (Node node : ImmutableList.copyOf(functionAnalysisResults.keySet())) {
if (scriptName.equals(NodeUtil.getSourceName(node))) {
functionAnalysisResults.remove(node);
}
}
(new FirstOrderFunctionAnalyzer(
compiler, functionAnalysisResults)).process(null, scriptRoot);
// TODO(bashir): Variable declaration is not the only side effect of last
// global scope generation but
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> need to special case ActiveXObject
// but this remains here until we can get the extern forks
// cleaned up.
declareNativeValueType(s, "ActiveXObject", FUNCTION_INSTANCE_TYPE);
return s;
}
private void declareNativeFunctionType(Scope scope, JSTypeNative tId) {
FunctionType t = typeRegistry.getNativeFunctionType(tId);
declareNativeType(scope, t.getInstanceType().getReferenceName(), t);
declareNativeType(
scope, t.getPrototype().getReferenceName(), t.getPrototype());
}
private void declareNativeValueType(Scope scope, String name,
JSTypeNative tId) {
declareNativeType(scope, name, typeRegistry.getNativeType(tId));
}
private static void declareNativeType(Scope scope, String name, JSType t) {
scope.declare(name, null, t, null, false);
}
private static class DiscoverEnumsAndTypedefs
extends AbstractShallowStatementCallback {
private final JSTypeRegistry registry;
DiscoverEnumsAndTypedefs(JSTypeRegistry registry) {
this.registry = registry;
}
@Override
public void visit(NodeTraversal t, Node node, Node parent) {
switch (node.getType()) {
case Token.VAR:
for (Node child = node.getFirstChild();
child != null; child = child.getNext()) {
identifyNameNode(
child, NodeUtil.getBestJSDocInfo(child));
}
break;
case Token.EXPR_RESULT:
Node firstChild = node.getFirstChild();
if (firstChild.isAssign()) {
identifyNameNode(
firstChild.getFirstChild(), firstChild.getJSDocInfo());
} else {
identifyNameNode(
firstChild, firstChild.getJSDocInfo());
}
break;
}
}
private void identifyNameNode(
Node nameNode, JSDocInfo info) {
if (nameNode.isQualifiedName()) {
if (info != null) {
if (info.hasEnumParameterType()) {
registry.identifyNonNullableName(nameNode.getQualifiedName());
} else if (info.hasTypedefType()) {
registry.identifyNonNullableName(nameNode.getQualifiedName());
}
}
}
}
}
private
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>Traverse(NodeTraversal t, Node n, Node parent) {
inputId = t.getInputId();
if (n.isFunction() ||
n.isScript()) {
Preconditions.checkNotNull(inputId);
sourceName = NodeUtil.getSourceName(n);
}
// We do want to traverse the name of a named function, but we don't
// want to traverse the arguments or body.
boolean descend = parent == null || !parent.isFunction() ||
n == parent.getFirstChild() || parent == scope.getRootNode();
if (descend) {
// Handle hoisted functions on pre-order traversal, so that they
// get hit before other things in the scope.
if (NodeUtil.isStatementParent(n)) {
for (Node child = n.getFirstChild();
child != null;
child = child.getNext()) {
if (NodeUtil.isHoistedFunctionDeclaration(child)) {
defineFunctionLiteral(child);
}
}
}
}
return descend;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
inputId = t.getInputId();
attachLiteralTypes(n);
switch (n.getType()) {
case Token.CALL:
checkForClassDefiningCalls(t, n);
checkForCallingConventionDefiningCalls(n, delegateCallingConventions);
break;
case Token.FUNCTION:
if (t.getInput() == null || !t.getInput().isExtern()) {
nonExternFunctions.add(n);
}
// Hoisted functions are handled during pre-traversal.
if (!NodeUtil.isHoistedFunctionDeclaration(n)) {
defineFunctionLiteral(n);
}
break;
case Token.ASSIGN:
// Handle initialization of properties.
Node firstChild = n.getFirstChild();
if (firstChild.isGetProp() &&
firstChild.isQualifiedName()) {
maybeDeclareQualifiedName(t, n.getJSDocInfo(),
firstChild, n, firstChild.getNext());
}
break;
case Token.CATCH:
defineCatch(n);
break;
case Token.VAR:
defineVar(n);
break;
case Token.GETPROP:
// Handle stubbed properties.
if (parent.isExprResult() &&
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>
n.isQualifiedName()) {
maybeDeclareQualifiedName(t, n.getJSDocInfo(), n, parent, null);
}
break;
}
// Analyze any @lends object literals in this statement.
if (n.getParent() != null && NodeUtil.isStatement(n) &&
lentObjectLiterals != null) {
for (Node objLit : lentObjectLiterals) {
defineObjectLiteral(objLit);
}
lentObjectLiterals.clear();
}
}
private void attachLiteralTypes(Node n) {
switch (n.getType()) {
case Token.NULL:
n.setJSType(getNativeType(NULL_TYPE));
break;
case Token.VOID:
n.setJSType(getNativeType(VOID_TYPE));
break;
case Token.STRING:
n.setJSType(getNativeType(STRING_TYPE));
break;
case Token.NUMBER:
n.setJSType(getNativeType(NUMBER_TYPE));
break;
case Token.TRUE:
case Token.FALSE:
n.setJSType(getNativeType(BOOLEAN_TYPE));
break;
case Token.REGEXP:
n.setJSType(getNativeType(REGEXP_TYPE));
break;
case Token.OBJECTLIT:
JSDocInfo info = n.getJSDocInfo();
if (info != null &&
info.getLendsName() != null) {
if (lentObjectLiterals == null) {
lentObjectLiterals = Lists.newArrayList();
}
lentObjectLiterals.add(n);
} else {
defineObjectLiteral(n);
}
break;
// NOTE(nicksantos): If we ever support Array tuples,
// we will need to put ARRAYLIT here as well.
}
}
private void defineObjectLiteral(Node objectLit) {
// Handle the @lends annotation.
JSType type = null;
JSDocInfo info = objectLit.getJSDocInfo();
if (info != null && info.getLendsName() != null) {
String lendsName = info.getLendsName();
Var lendsVar = scope.getVar(lendsName);
if (lendsVar == null
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> be inferred.
*/
private void defineSlot(Node name, Node parent, JSType type) {
defineSlot(name, parent, type, type == null);
}
/**
* Defines a typed variable. The defining node will be annotated with the
* variable's type of {@link JSTypeNative#UNKNOWN_TYPE} if its type is
* inferred.
*
* Slots may be any variable or any qualified name in the global scope.
*
* @param n the defining NAME or GETPROP node.
* @param parent the {@code n}'s parent.
* @param type the variable's type. It may be {@code null} if
* {@code inferred} is {@code true}.
*/
void defineSlot(Node n, Node parent, JSType type, boolean inferred) {
Preconditions.checkArgument(inferred || type != null);
// Only allow declarations of NAMEs and qualified names.
// Object literal keys will have to compute their names themselves.
if (n.isName()) {
Preconditions.checkArgument(
parent.isFunction() ||
parent.isVar() ||
parent.isParamList() ||
parent.isCatch());
} else {
Preconditions.checkArgument(
n.isGetProp() &&
(parent.isAssign() ||
parent.isExprResult()));
}
defineSlot(n, parent, n.getQualifiedName(), type, inferred);
}
/**
* Defines a symbol in the current scope.
*
* @param n the defining NAME or GETPROP or object literal key node.
* @param parent the {@code n}'s parent.
* @param variableName The name that this should be known by.
* @param type the variable's type. It may be {@code null} if
* {@code inferred} is {@code true}.
* @param inferred Whether the type is inferred or declared.
*/
void defineSlot(Node n, Node parent, String variableName,
JSType type, boolean inferred) {
Preconditions.checkArgument(!variableName.isEmpty());
boolean isGlobalVar = n.isName() && scope.isGlobal();
boolean shouldDeclareOnGlobalThis =
isGlobalVar &&
(parent.isVar() ||
parent.isFunction());
// If n is a property, then we should really declare it in the
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>
// scope where the root object appears. This helps out people
// who declare "global" names in an anonymous namespace.
Scope scopeToDeclareIn = scope;
if (n.isGetProp() && !scope.isGlobal() &&
isQnameRootedInGlobalScope(n)) {
Scope globalScope = scope.getGlobalScope();
// don't try to declare in the global scope if there's
// already a symbol there with this name.
if (!globalScope.isDeclared(variableName, false)) {
scopeToDeclareIn = scope.getGlobalScope();
}
}
// The input may be null if we are working with a AST snippet. So read
// the extern info from the node.
Var newVar = null;
// declared in closest scope?
CompilerInput input = compiler.getInput(inputId);
if (scopeToDeclareIn.isDeclared(variableName, false)) {
Var oldVar = scopeToDeclareIn.getVar(variableName);
newVar = validator.expectUndeclaredVariable(
sourceName, input, n, parent, oldVar, variableName, type);
} else {
if (type != null) {
setDeferredType(n, type);
}
newVar =
scopeToDeclareIn.declare(variableName, n, type, input, inferred);
if (type instanceof EnumType) {
Node initialValue = newVar.getInitialValue();
boolean isValidValue = initialValue != null &&
(initialValue.isObjectLit() ||
initialValue.isQualifiedName());
if (!isValidValue) {
compiler.report(JSError.make(sourceName, n, ENUM_INITIALIZER));
}
}
}
// We need to do some additional work for constructors and interfaces.
FunctionType fnType = JSType.toMaybeFunctionType(type);
if (fnType != null &&
// We don't want to look at empty function types.
!type.isEmptyType()) {
// We want to make sure that when we declare a new instance type
// (with @constructor) that there's actually a ctor for it.
// This doesn't apply to structural constructors (like
// function(new:Array). Checking the constructed type against
// the variable name is a sufficient check for this.
if ((fnType.isConstructor() || fnType.is
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>
delegateSuperObject, delegateBaseObject, delegatorObject,
delegateProxy, findDelegate);
delegateProxyPrototypes.add(delegateProxy.getPrototype());
}
}
}
/**
* Declare the symbol for a qualified name in the global scope.
*
* @param info The doc info for this property.
* @param n A top-level GETPROP node (it should not be contained inside
* another GETPROP).
* @param parent The parent of {@code n}.
* @param rhsValue The node that {@code n} is being initialized to,
* or {@code null} if this is a stub declaration.
*/
void maybeDeclareQualifiedName(NodeTraversal t, JSDocInfo info,
Node n, Node parent, Node rhsValue) {
Node ownerNode = n.getFirstChild();
String ownerName = ownerNode.getQualifiedName();
String qName = n.getQualifiedName();
String propName = n.getLastChild().getString();
Preconditions.checkArgument(qName != null && ownerName != null);
// Precedence of type information on GETPROPs:
// 1) @type annotation / @enum annotation
// 2) ASSIGN to FUNCTION literal
// 3) @param/@return annotation (with no function literal)
// 4) ASSIGN to something marked @const
// 5) ASSIGN to anything else
//
// 1, 3, and 4 are declarations, 5 is inferred, and 2 is a declaration iff
// the function has JsDoc or has not been declared before.
//
// FUNCTION literals are special because TypedScopeCreator is very smart
// about getting as much type information as possible for them.
// Determining type for #1 + #2 + #3 + #4
JSType valueType = getDeclaredType(info, n, rhsValue);
if (valueType == null && rhsValue != null) {
// Determining type for #5
valueType = rhsValue.getJSType();
}
// Function prototypes are special.
// It's a common JS idiom to do:
// F.prototype = { ... };
// So if F does not have an explicitly declared super type,
// allow F.prototype to be redefined arbitrarily.
if ("prototype".equals(propName)) {
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> Var qVar = scope.getVar(qName);
if (qVar != null) {
// If the programmer has declared that F inherits from Super,
// and they assign F.prototype to an object literal,
// then they are responsible for making sure that the object literal's
// implicit prototype is set up appropriately. We just obey
// the @extends tag.
ObjectType qVarType = ObjectType.cast(qVar.getType());
if (qVarType != null &&
rhsValue != null &&
rhsValue.isObjectLit()) {
typeRegistry.resetImplicitPrototype(
rhsValue.getJSType(), qVarType.getImplicitPrototype());
} else if (!qVar.isTypeInferred()) {
// If the programmer has declared that F inherits from Super,
// and they assign F.prototype to some arbitrary expression,
// there's not much we can do. We just ignore the expression,
// and hope they've annotated their code in a way to tell us
// what props are going to be on that prototype.
return;
}
qVar.getScope().undeclare(qVar);
}
}
if (valueType == null) {
if (parent.isExprResult()) {
stubDeclarations.add(new StubDeclaration(
n,
t.getInput() != null && t.getInput().isExtern(),
ownerName));
}
return;
}
boolean inferred = isQualifiedNameInferred(
qName, n, info, rhsValue, valueType);
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
ownerType.defineDeclaredProperty(propName, valueType, n);
}
}
// If the property is already declared, the error will be
// caught when we try to declare it in the current scope.
define
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>Slot(n, parent, valueType, inferred);
} else if (rhsValue != null && rhsValue.isTrue()) {
// We declare these for delegate proxy method properties.
ObjectType ownerType = getObjectSlot(ownerName);
FunctionType ownerFnType = JSType.toMaybeFunctionType(ownerType);
if (ownerFnType != null) {
JSType ownerTypeOfThis = ownerFnType.getTypeOfThis();
String delegateName = codingConvention.getDelegateSuperclassName();
JSType delegateType = delegateName == null ?
null : typeRegistry.getType(delegateName);
if (delegateType != null &&
ownerTypeOfThis.isSubtype(delegateType)) {
defineSlot(n, parent, getNativeType(BOOLEAN_TYPE), true);
}
}
}
}
/**
* Determines whether a qualified name is inferred.
* NOTE(nicksantos): Determining whether a property is declared or not
* is really really obnoxious.
*
* The problem is that there are two (equally valid) coding styles:
*
* (function() {
* /* The authoritative definition of goog.bar. /
* goog.bar = function() {};
* })();
*
* function f() {
* goog.bar();
* /* Reset goog.bar to a no-op. /
* goog.bar = function() {};
* }
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> Var slot = scope.getSlot(className);
JSType classType = slot == null ? null : slot.getType();
if (classType != null
&& (classType.isConstructor() || classType.isInterface())) {
return false;
}
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
// Check if this is in a conditional block.
// Functions assigned in conditional blocks are inferred.
for (Node current = n.getParent();
!(current.isScript() || current.isFunction());
current = current.getParent()) {
if (NodeUtil.isControlStructure(current)) {
return true;
}
}
// Check if this is assigned in an inner scope.
// Functions assigned in inner scopes are inferred.
AstFunctionContents contents =
getFunctionAnalysisResults(scope.getRootNode());
if (contents == null ||
!contents.getEscapedQualifiedNames().contains(qName)) {
return false;
}
}
}
return inferred;
}
private boolean isConstantSymbol(JSDocInfo info, Node node) {
if (info != null && info.isConstant()) {
return true;
}
switch (node.getType()) {
case Token.NAME:
return NodeUtil.isConstantByConvention(
compiler.getCodingConvention(), node, node.getParent());
case Token.GETPROP:
return node.isQualifiedName() && NodeUtil.isConstantByConvention(
compiler.getCodingConvention(), node.getLastChild(), node);
}
return false;
}
/**
* Find the ObjectType associated with the given slot.
* @param slotName The name of the slot to find the type in.
* @return An object type, or null if this slot does not contain an object.
*/
private ObjectType getObjectSlot(
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>String slotName) {
Var ownerVar = scope.getVar(slotName);
if (ownerVar != null) {
JSType ownerVarType = ownerVar.getType();
return ObjectType.cast(ownerVarType == null ?
null : ownerVarType.restrictByNotNullOrUndefined());
}
return null;
}
/**
* Resolve any stub declarations to unknown types if we could not
* find types for them during traversal.
*/
void resolveStubDeclarations() {
for (StubDeclaration stub : stubDeclarations) {
Node n = stub.node;
Node parent = n.getParent();
String qName = n.getQualifiedName();
String propName = n.getLastChild().getString();
String ownerName = stub.ownerName;
boolean isExtern = stub.isExtern;
if (scope.isDeclared(qName, false)) {
continue;
}
// If we see a stub property, make sure to register this property
// in the type registry.
ObjectType ownerType = getObjectSlot(ownerName);
defineSlot(n, parent, unknownType, true);
if (ownerType != null &&
(isExtern || ownerType.isFunctionPrototypeType())) {
// If this is a stub for a prototype, just declare it
// as an unknown type. These are seen often in externs.
ownerType.defineInferredProperty(
propName, unknownType, n);
} else {
typeRegistry.registerPropertyOnType(
propName, ownerType == null ? unknownType : ownerType);
}
}
}
/**
* Collects all declared properties in a function, and
* resolves them relative to the global scope.
*/
private final class CollectProperties
extends AbstractShallowStatementCallback {
private final JSType thisType;
CollectProperties(JSType thisType) {
this.thisType = thisType;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isExprResult()) {
Node child = n.getFirstChild();
switch (child.getType()) {
case Token.ASSIGN:
maybeCollectMember(child.getFirstChild(), child,
child.getLastChild());
break;
case Token.GETPROP:
maybeCollectMember(child, child, null);
break;
}
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> to the
* global symbol table.
*
* @param t The current traversal.
* @param n The node being visited.
* @param parent The parent of n
*/
@Override public void visit(NodeTraversal t, Node n, Node parent) {
super.visit(t, n, parent);
switch (n.getType()) {
case Token.VAR:
// Handle typedefs.
if (n.hasOneChild()) {
checkForTypedef(t, n.getFirstChild(), n.getJSDocInfo());
}
break;
}
}
@Override
void maybeDeclareQualifiedName(
NodeTraversal t, JSDocInfo info,
Node n, Node parent, Node rhsValue) {
checkForTypedef(t, n, info);
super.maybeDeclareQualifiedName(t, info, n, parent, rhsValue);
}
/**
* Handle typedefs.
* @param t The current traversal.
* @param candidate A qualified name node.
* @param info JSDoc comments.
*/
private void checkForTypedef(
NodeTraversal t, Node candidate, JSDocInfo info) {
if (info == null || !info.hasTypedefType()) {
return;
}
String typedef = candidate.getQualifiedName();
if (typedef == null) {
return;
}
// TODO(nicksantos|user): This is a terrible, terrible hack
// to bail out on recursive typedefs. We'll eventually need
// to handle these properly.
typeRegistry.declareType(typedef, unknownType);
JSType realType = info.getTypedefType().evaluate(scope, typeRegistry);
if (realType == null) {
compiler.report(
JSError.make(
t.getSourceName(), candidate, MALFORMED_TYPEDEF, typedef));
}
typeRegistry.overwriteDeclaredType(typedef, realType);
if (candidate.isGetProp()) {
defineSlot(candidate, candidate.getParent(),
getNativeType(NO_TYPE), false);
}
}
} // end GlobalScopeBuilder
/**
* A shallow traversal of a local scope to find all arguments and
* local variables.
*/
private final class LocalScopeBuilder extends AbstractScopeBuilder {
/**
* @param scope The scope that we're building.
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>
*/
private LocalScopeBuilder(Scope scope) {
super(scope);
}
/**
* Traverse the scope root and build it.
*/
void build() {
NodeTraversal.traverse(compiler, scope.getRootNode(), this);
AstFunctionContents contents =
getFunctionAnalysisResults(scope.getRootNode());
if (contents != null) {
for (String varName : contents.getEscapedVarNames()) {
Var v = scope.getVar(varName);
Preconditions.checkState(v.getScope() == scope);
v.markEscaped();
}
for (Multiset.Entry<String> entry :
contents.getAssignedNameCounts().entrySet()) {
Var v = scope.getVar(entry.getElement());
Preconditions.checkState(v.getScope() == scope);
if (entry.getCount() == 1) {
v.markAssignedExactlyOnce();
}
}
}
}
/**
* Visit a node in a local scope, and add any local variables or catch
* parameters into the local symbol table.
*
* @param t The node traversal.
* @param n The node being visited.
* @param parent The parent of n
*/
@Override public void visit(NodeTraversal t, Node n, Node parent) {
if (n == scope.getRootNode()) {
return;
}
if (n.isParamList() && parent == scope.getRootNode()) {
handleFunctionInputs(parent);
return;
}
super.visit(t, n, parent);
}
/** Handle bleeding functions and function parameters. */
private void handleFunctionInputs(Node fnNode) {
// Handle bleeding functions.
Node fnNameNode = fnNode.getFirstChild();
String fnName = fnNameNode.getString();
if (!fnName.isEmpty()) {
Scope.Var fnVar = scope.getVar(fnName);
if (fnVar == null ||
// Make sure we're not touching a native function. Native
// functions aren't bleeding, but may not have a declaration
// node.
(fnVar.getNameNode() != null &&
// Make sure that the function is actually bleeding by checking
// if has already been declared.
fnVar.getInitialValue() != fnNode)) {
define
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>Slot(fnNameNode, fnNode, fnNode.getJSType(), false);
}
}
declareArguments(fnNode);
}
/**
* Declares all of a function's arguments.
*/
private void declareArguments(Node functionNode) {
Node astParameters = functionNode.getFirstChild().getNext();
Node iifeArgumentNode = null;
if (NodeUtil.isCallOrNewTarget(functionNode)) {
iifeArgumentNode = functionNode.getNext();
}
FunctionType functionType =
JSType.toMaybeFunctionType(functionNode.getJSType());
if (functionType != null) {
Node jsDocParameters = functionType.getParametersNode();
if (jsDocParameters != null) {
Node jsDocParameter = jsDocParameters.getFirstChild();
for (Node astParameter : astParameters.children()) {
JSType paramType = jsDocParameter == null ?
unknownType : jsDocParameter.getJSType();
boolean inferred = paramType == null || paramType == unknownType;
if (iifeArgumentNode != null && inferred) {
String argumentName = iifeArgumentNode.getQualifiedName();
Var argumentVar =
argumentName == null || scope.getParent() == null
? null : scope.getParent().getVar(argumentName);
if (argumentVar != null && !argumentVar.isTypeInferred()) {
paramType = argumentVar.getType();
}
}
if (paramType == null) {
paramType = unknownType;
}
defineSlot(astParameter, functionNode, paramType, inferred);
if (jsDocParameter != null) {
jsDocParameter = jsDocParameter.getNext();
}
if (iifeArgumentNode != null) {
iifeArgumentNode = iifeArgumentNode.getNext();
}
}
}
}
} // end declareArguments
} // end LocalScopeBuilder
/**
* Does a first-order function analysis that just looks at simple things
* like what variables are escaped, and whether 'this' is used.
*/
private static class FirstOrderFunctionAnalyzer
extends AbstractScopedCallback implements CompilerPass {
private final AbstractCompiler compiler;
private final Map<Node, AstFunctionContents> data;
FirstOrderFunctionAnalyzer(
AbstractCompiler compiler, Map<Node, AstFunctionContents> outParam) {
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS>
this.compiler = compiler;
this.data = outParam;
}
@Override public void process(Node externs, Node root) {
if (externs == null) {
NodeTraversal.traverse(compiler, root, this);
} else {
NodeTraversal.traverseRoots(
compiler, ImmutableList.of(externs, root), this);
}
}
@Override public void enterScope(NodeTraversal t) {
if (!t.inGlobalScope()) {
Node n = t.getScopeRoot();
data.put(n, new AstFunctionContents(n));
}
}
@Override public void visit(NodeTraversal t, Node n, Node parent) {
if (t.inGlobalScope()) {
return;
}
if (n.isReturn() && n.getFirstChild() != null) {
data.get(t.getScopeRoot()).recordNonEmptyReturn();
}
if (t.getScopeDepth() <= 1) {
// The first-order function analyzer looks at two types of variables:
//
// 1) Local variables that are assigned in inner scopes ("escaped vars")
//
// 2) Local variables that are assigned more than once.
//
// We treat all global variables as escaped by default, so there's
// no reason to do this extra computation for them.
return;
}
if (n.isName() && NodeUtil.isLValue(n) &&
// Be careful of bleeding functions, which create variables
// in the inner scope, not the scope where the name appears.
!NodeUtil.isBleedingFunctionName(n)) {
String name = n.getString();
Scope scope = t.getScope();
Var var = scope.getVar(name);
if (var != null) {
Scope ownerScope = var.getScope();
if (ownerScope.isLocal()) {
data.get(ownerScope.getRootNode()).recordAssignedName(name);
}
if (scope != ownerScope && ownerScope.isLocal()) {
data.get(ownerScope.getRootNode()).recordEscapedVarName(name);
}
}
} else if (n.isGetProp() && n.isUnscopedQualifiedName() &&
NodeUtil.isLValue(n)) {
String name =
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> function (for interfaces)
* It is only relevant for constructors. May not be {@code null}.
*/
private List<ObjectType> extendedInterfaces = ImmutableList.of();
/**
* The types which are subtypes of this function. It is only relevant for
* constructors and may be {@code null}.
*/
private List<FunctionType> subTypes;
/** Creates an instance for a function that might be a constructor. */
FunctionType(JSTypeRegistry registry, String name, Node source,
ArrowType arrowType, JSType typeOfThis,
TemplateTypeMap templateTypeMap,
boolean isConstructor, boolean nativeType) {
super(registry, name,
registry.getNativeObjectType(JSTypeNative.FUNCTION_INSTANCE_TYPE),
nativeType, templateTypeMap);
setPrettyPrint(true);
Preconditions.checkArgument(source == null ||
Token.FUNCTION == source.getType());
Preconditions.checkNotNull(arrowType);
this.source = source;
if (isConstructor) {
this.kind = Kind.CONSTRUCTOR;
this.propAccess = PropAccess.ANY;
this.typeOfThis = typeOfThis != null ?
typeOfThis : new InstanceObjectType(registry, this, nativeType);
} else {
this.kind = Kind.ORDINARY;
this.typeOfThis = typeOfThis != null ?
typeOfThis :
registry.getNativeObjectType(JSTypeNative.UNKNOWN_TYPE);
}
this.call = arrowType;
}
/** Creates an instance for a function that is an interface. */
private FunctionType(JSTypeRegistry registry, String name, Node source,
TemplateTypeMap typeParameters) {
super(registry, name,
registry.getNativeObjectType(JSTypeNative.FUNCTION_INSTANCE_TYPE),
false, typeParameters);
setPrettyPrint(true);
Preconditions.checkArgument(source == null ||
Token.FUNCTION == source.getType());
Preconditions.checkArgument(name != null);
this.source = source;
this.call = new ArrowType(registry, new Node(Token.PARAM_LIST), null);
this.kind = Kind.INTERFACE;
this.typeOfThis = new InstanceObjectType(registry, this);
}
/** Creates an instance for a function that is an interface. */
static FunctionType forInterface(
JSTypeRegistry registry, String name, Node source,
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> private FunctionType tryMergeFunctionPiecewise(
FunctionType other, boolean leastSuper) {
Node newParamsNode = null;
if (call.hasEqualParameters(other.call, EquivalenceMethod.IDENTITY)) {
newParamsNode = call.parameters;
} else {
// If the parameters are not equal, don't try to merge them.
// Someday, we should try to merge the individual params.
return null;
}
JSType newReturnType = leastSuper ?
call.returnType.getLeastSupertype(other.call.returnType) :
call.returnType.getGreatestSubtype(other.call.returnType);
JSType newTypeOfThis = null;
if (isEquivalent(typeOfThis, other.typeOfThis)) {
newTypeOfThis = typeOfThis;
} else {
JSType maybeNewTypeOfThis = leastSuper ?
typeOfThis.getLeastSupertype(other.typeOfThis) :
typeOfThis.getGreatestSubtype(other.typeOfThis);
newTypeOfThis = maybeNewTypeOfThis;
}
boolean newReturnTypeInferred =
call.returnTypeInferred || other.call.returnTypeInferred;
return new FunctionType(
registry, null, null,
new ArrowType(
registry, newParamsNode, newReturnType, newReturnTypeInferred),
newTypeOfThis, null, false, false);
}
/**
* Given a constructor or an interface type, get its superclass constructor
* or {@code null} if none exists.
*/
public FunctionType getSuperClassConstructor() {
Preconditions.checkArgument(isConstructor() || isInterface());
ObjectType maybeSuperInstanceType = getPrototype().getImplicitPrototype();
if (maybeSuperInstanceType == null) {
return null;
}
return maybeSuperInstanceType.getConstructor();
}
/**
* Given an interface and a property, finds the top-most super interface
* that has the property defined (including this interface).
*/
public static ObjectType getTopDefiningInterface(ObjectType type,
String propertyName) {
ObjectType foundType = null;
if (type.hasProperty(propertyName)) {
foundType = type;
}
for (ObjectType interfaceType : type.getCtorExtendedInterfaces()) {
if (interfaceType.hasProperty(propertyName)) {
foundType = getTopDefiningInterface(interfaceType,
Closure, 173
<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB>
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
<CHANGES>
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
<FILEE>
<FILEB>
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.RETURN:
return tryReduceReturn(node);
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
<CHANGES>
<CHANGEE>
default:
return node; //<SCANS> propertyName);
}
}
return foundType;
}
/**
* Given a constructor or an interface type and a property, finds the
* top-most superclass that has the property defined (including this
* constructor).
*/
public ObjectType getTopMostDefiningType(String propertyName) {
Preconditions.checkState(isConstructor() || isInterface());
Preconditions.checkArgument(getInstanceType().hasProperty(propertyName));
FunctionType ctor = this;
if (isInterface()) {
return getTopDefiningInterface(getInstanceType(), propertyName);
}
ObjectType topInstanceType = null;
do {
topInstanceType = ctor.getInstanceType();
ctor = ctor.getSuperClassConstructor();
} while (ctor != null
&& ctor.getPrototype().hasProperty(propertyName));
return topInstanceType;
}
/**
* Two function types are equal if their signatures match. Since they don't
* have signatures, two interfaces are equal if their names match.
*/
boolean checkFunctionEquivalenceHelper(
FunctionType that, EquivalenceMethod eqMethod) {
if (isConstructor()) {
if (that.isConstructor()) {
return this == that;
}
return false;
}
if (isInterface()) {
if (that.isInterface()) {
return getReferenceName().equals(that.getReferenceName());
}
return false;
}
if (that.isInterface()) {
return false;
}
return typeOfThis.checkEquivalenceHelper(that.typeOfThis, eqMethod) &&
call.checkArrowEquivalenceHelper(that.call, eqMethod);
}
@Override
public int hashCode() {
return isInterface() ? getReferenceName().hashCode() : call.hashCode();
}
public boolean hasEqualCallType(FunctionType otherType) {
return this.call.checkArrowEquivalenceHelper(
otherType.call, EquivalenceMethod.IDENTITY);
}
/**
* Informally, a function is represented by
* {@code function (params): returnType} where the {@code params} is a comma
* separated list of types, the first one being a special
* {@code this:T} if the function expects a known type for {@code this}.
*/
@Override
String toStringHelper(boolean forAnnotations) {
if (!isPrettyPrint() ||
this ==